@zaptcha/widget 1.0.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/README.md ADDED
@@ -0,0 +1,496 @@
1
+ # Zero-Captcha
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@your-org/zero-captcha.svg)](https://www.npmjs.com/package/@your-org/zero-captcha)
4
+ [![deno land](https://img.shields.io/badge/available%20on-deno.land-lightgrey)](https://deno.land/x/zero_captcha)
5
+ [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
7
+
8
+ A zero-knowledge CAPTCHA Web Component with WASM-based proof-of-work verification. Secure, accessible, and framework-agnostic.
9
+
10
+ ## Features
11
+
12
+ ✨ **Zero-Knowledge Proof** - Server doesn't learn anything about solver
13
+ 🔐 **WASM Proof-of-Work** - Client-side computational verification
14
+ 🎨 **Fully Themeable** - CSS custom properties for complete styling control
15
+ ♿ **Accessible** - ARIA attributes, keyboard navigation, semantic HTML
16
+ 📦 **Framework Agnostic** - Works with React, Vue, Angular, Svelte, plain JS
17
+ ⚡ **Lightweight** - ~8KB gzipped (with WASM)
18
+ 🚀 **Deno & Node.js** - Published to both ecosystems
19
+
20
+ ## Installation
21
+
22
+ ### npm (Node.js)
23
+
24
+ ```bash
25
+ npm install @your-org/zero-captcha
26
+ ```
27
+
28
+ ```bash
29
+ yarn add @your-org/zero-captcha
30
+ ```
31
+
32
+ ```bash
33
+ pnpm add @your-org/zero-captcha
34
+ ```
35
+
36
+ ### Deno
37
+
38
+ ```typescript
39
+ import ZeroCaptcha from "https://deno.land/x/zero_captcha@1.0.0/src/index.ts";
40
+ ```
41
+
42
+ ### Browser (CDN)
43
+
44
+ ```html
45
+ <script type="module">
46
+ import ZeroCaptcha from "https://cdn.jsdelivr.net/npm/@your-org/zero-captcha@1.0.0/dist/index.js";
47
+ </script>
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ### Basic HTML
53
+
54
+ ```html
55
+ <!DOCTYPE html>
56
+ <html>
57
+ <head>
58
+ <style>
59
+ :root {
60
+ --zaptcha-accent: #4f46e5;
61
+ --zaptcha-success: #10b981;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <form id="myForm">
67
+ <input type="text" placeholder="Your name" required />
68
+
69
+ <zero-captcha
70
+ base-url="https://api.example.com"
71
+ config-id="AAAA"
72
+ ></zero-captcha>
73
+
74
+ <button type="submit">Submit</button>
75
+ </form>
76
+
77
+ <script type="module">
78
+ import ZeroCaptcha from '@your-org/zero-captcha';
79
+
80
+ const form = document.getElementById('myForm');
81
+ const captcha = form.querySelector('zero-captcha');
82
+ let token = null;
83
+
84
+ captcha.addEventListener('zaptcha-success', (e) => {
85
+ token = e.detail.token;
86
+ console.log('✓ CAPTCHA verified:', token);
87
+ });
88
+
89
+ captcha.addEventListener('zaptcha-fail', (e) => {
90
+ console.error('✗ CAPTCHA failed:', e.detail.reason);
91
+ });
92
+
93
+ form.addEventListener('submit', async (e) => {
94
+ e.preventDefault();
95
+
96
+ if (!token) {
97
+ alert('Please solve the CAPTCHA');
98
+ return;
99
+ }
100
+
101
+ // Send form with token to server
102
+ const formData = new FormData(form);
103
+ formData.append('captcha_token', token);
104
+
105
+ const response = await fetch('/api/submit', {
106
+ method: 'POST',
107
+ body: formData
108
+ });
109
+
110
+ if (response.ok) {
111
+ console.log('✓ Form submitted');
112
+ captcha.reset();
113
+ }
114
+ });
115
+ </script>
116
+ </body>
117
+ </html>
118
+ ```
119
+
120
+ ### React
121
+
122
+ ```jsx
123
+ import { useEffect, useRef, useState } from 'react';
124
+ import ZeroCaptcha from '@your-org/zero-captcha';
125
+
126
+ export function CaptchaForm() {
127
+ const captchaRef = useRef(null);
128
+ const [token, setToken] = useState(null);
129
+ const [loading, setLoading] = useState(false);
130
+
131
+ useEffect(() => {
132
+ const el = captchaRef.current;
133
+ if (!el) return;
134
+
135
+ const handleSuccess = (e) => setToken(e.detail.token);
136
+ const handleFail = (e) => console.error(e.detail.reason);
137
+
138
+ el.addEventListener('zaptcha-success', handleSuccess);
139
+ el.addEventListener('zaptcha-fail', handleFail);
140
+
141
+ return () => {
142
+ el.removeEventListener('zaptcha-success', handleSuccess);
143
+ el.removeEventListener('zaptcha-fail', handleFail);
144
+ };
145
+ }, []);
146
+
147
+ const handleSubmit = async (e) => {
148
+ e.preventDefault();
149
+ if (!token) return alert('Solve CAPTCHA first');
150
+
151
+ setLoading(true);
152
+ const res = await fetch('/api/submit', {
153
+ method: 'POST',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({ token })
156
+ });
157
+ setLoading(false);
158
+
159
+ if (res.ok) {
160
+ captchaRef.current?.reset();
161
+ setToken(null);
162
+ }
163
+ };
164
+
165
+ return (
166
+ <form onSubmit={handleSubmit}>
167
+ <input type="email" placeholder="Email" required />
168
+ <zero-captcha
169
+ ref={captchaRef}
170
+ base-url="https://api.example.com"
171
+ config-id="AAAA"
172
+ />
173
+ <button type="submit" disabled={!token || loading}>
174
+ {loading ? 'Submitting...' : 'Submit'}
175
+ </button>
176
+ </form>
177
+ );
178
+ }
179
+ ```
180
+
181
+ ### Vue 3
182
+
183
+ ```vue
184
+ <template>
185
+ <form @submit.prevent="handleSubmit">
186
+ <input v-model="email" type="email" placeholder="Email" required />
187
+
188
+ <zero-captcha
189
+ ref="captchaRef"
190
+ base-url="https://api.example.com"
191
+ config-id="AAAA"
192
+ @zaptcha-success="onSuccess"
193
+ @zaptcha-fail="onFail"
194
+ />
195
+
196
+ <button type="submit" :disabled="!token || loading">
197
+ {{ loading ? 'Submitting...' : 'Submit' }}
198
+ </button>
199
+ </form>
200
+ </template>
201
+
202
+ <script setup>
203
+ import { ref } from 'vue';
204
+ import ZeroCaptcha from '@your-org/zero-captcha';
205
+
206
+ const captchaRef = ref(null);
207
+ const email = ref('');
208
+ const token = ref(null);
209
+ const loading = ref(false);
210
+
211
+ const onSuccess = (e) => {
212
+ token.value = e.detail.token;
213
+ };
214
+
215
+ const onFail = (e) => {
216
+ console.error(e.detail.reason);
217
+ };
218
+
219
+ const handleSubmit = async () => {
220
+ if (!token.value) return;
221
+
222
+ loading.value = true;
223
+ const res = await fetch('/api/submit', {
224
+ method: 'POST',
225
+ headers: { 'Content-Type': 'application/json' },
226
+ body: JSON.stringify({ email: email.value, token: token.value })
227
+ });
228
+ loading.value = false;
229
+
230
+ if (res.ok) {
231
+ captchaRef.value?.reset();
232
+ token.value = null;
233
+ }
234
+ };
235
+ </script>
236
+ ```
237
+
238
+ ### Angular
239
+
240
+ ```typescript
241
+ import { Component, ViewChild, ElementRef } from '@angular/core';
242
+ import { FormsModule } from '@angular/forms';
243
+ import ZeroCaptcha from '@your-org/zero-captcha';
244
+
245
+ @Component({
246
+ selector: 'app-captcha-form',
247
+ template: `
248
+ <form (ngSubmit)="onSubmit()">
249
+ <input
250
+ [(ngModel)]="email"
251
+ name="email"
252
+ type="email"
253
+ placeholder="Email"
254
+ required
255
+ />
256
+
257
+ <zero-captcha
258
+ #captchaEl
259
+ base-url="https://api.example.com"
260
+ config-id="AAAA"
261
+ (zaptcha-success)="onSuccess($event)"
262
+ (zaptcha-fail)="onFail($event)"
263
+ />
264
+
265
+ <button
266
+ type="submit"
267
+ [disabled]="!token || loading"
268
+ >
269
+ {{ loading ? 'Submitting...' : 'Submit' }}
270
+ </button>
271
+ </form>
272
+ `,
273
+ standalone: true,
274
+ imports: [FormsModule]
275
+ })
276
+ export class CaptchaFormComponent {
277
+ @ViewChild('captchaEl') captchaEl!: ElementRef<any>;
278
+
279
+ email = '';
280
+ token: string | null = null;
281
+ loading = false;
282
+
283
+ onSuccess(event: CustomEvent<{ token: string }>) {
284
+ this.token = event.detail.token;
285
+ }
286
+
287
+ onFail(event: CustomEvent<{ reason: string }>) {
288
+ console.error(event.detail.reason);
289
+ }
290
+
291
+ async onSubmit() {
292
+ if (!this.token) return;
293
+
294
+ this.loading = true;
295
+ const res = await fetch('/api/submit', {
296
+ method: 'POST',
297
+ headers: { 'Content-Type': 'application/json' },
298
+ body: JSON.stringify({ email: this.email, token: this.token })
299
+ });
300
+ this.loading = false;
301
+
302
+ if (res.ok) {
303
+ this.captchaEl.nativeElement.reset();
304
+ this.token = null;
305
+ }
306
+ }
307
+ }
308
+ ```
309
+
310
+ ### Deno Fresh
311
+
312
+ ```typescript
313
+ // routes/form.tsx
314
+ import { h, Fragment } from "preact";
315
+ import { useState, useRef } from "preact/hooks";
316
+
317
+ export default function FormPage() {
318
+ const [token, setToken] = useState<string | null>(null);
319
+ const captchaRef = useRef<any>(null);
320
+
321
+ return (
322
+ <form onSubmit={(e) => {
323
+ e.preventDefault();
324
+ if (!token) alert('Solve CAPTCHA');
325
+ }}>
326
+ <input type="email" placeholder="Email" required />
327
+
328
+ <zero-captcha
329
+ ref={captchaRef}
330
+ base-url="https://api.example.com"
331
+ config-id="AAAA"
332
+ onzaptcha-success={(e: any) => setToken(e.detail.token)}
333
+ />
334
+
335
+ <button type="submit" disabled={!token}>Submit</button>
336
+ </form>
337
+ );
338
+ }
339
+ ```
340
+
341
+ ## Styling
342
+
343
+ Customize appearance with CSS custom properties:
344
+
345
+ ```css
346
+ :root {
347
+ /* Light mode (defaults) */
348
+ --zaptcha-bg: #ffffff;
349
+ --zaptcha-border: #e5e7eb;
350
+ --zaptcha-border-hover: #d1d5db;
351
+ --zaptcha-border-focus: #4f46e5;
352
+ --zaptcha-text: #374151;
353
+ --zaptcha-text-muted: #9ca3af;
354
+ --zaptcha-accent: #4f46e5;
355
+ --zaptcha-success: #10b981;
356
+ --zaptcha-shadow: 0 2px 6px rgba(0,0,0,0.04);
357
+ --zaptcha-radius: 8px;
358
+ }
359
+
360
+ @media (prefers-color-scheme: dark) {
361
+ :root {
362
+ --zaptcha-bg: #1f2937;
363
+ --zaptcha-border: #374151;
364
+ --zaptcha-border-hover: #4b5563;
365
+ --zaptcha-border-focus: #818cf8;
366
+ --zaptcha-text: #f3f4f6;
367
+ --zaptcha-text-muted: #6b7280;
368
+ --zaptcha-accent: #818cf8;
369
+ --zaptcha-success: #34d399;
370
+ --zaptcha-shadow: 0 2px 6px rgba(0,0,0,0.3);
371
+ }
372
+ }
373
+
374
+ zero-captcha {
375
+ width: 100%;
376
+ max-width: 210px;
377
+ }
378
+ ```
379
+
380
+ ## API Reference
381
+
382
+ ### Attributes
383
+
384
+ | Name | Type | Required | Description |
385
+ |------|------|----------|-------------|
386
+ | `base-url` | string | ✓ | Backend API base URL |
387
+ | `config-id` | string | ✓ | Base64url-encoded config ID |
388
+ | `wasm-glue` | string | | Custom WASM glue module path |
389
+ | `wasm-bin` | string | | Custom WASM binary path |
390
+
391
+ ### Events
392
+
393
+ | Event | Detail | Description |
394
+ |-------|--------|-------------|
395
+ | `zaptcha-success` | `{ token: string }` | CAPTCHA solved, token generated |
396
+ | `zaptcha-fail` | `{ reason: string }` | Solve failed |
397
+ | `zaptcha-reset` | `{}` | Widget reset to idle state |
398
+ | `zaptcha-progress` | `{ index, nonce, total, pct }` | Solving progress update |
399
+
400
+ ### Methods
401
+
402
+ ```typescript
403
+ element.reset(): void
404
+ // Reset widget to idle state
405
+ ```
406
+
407
+ ## Backend Integration
408
+
409
+ Your backend needs two endpoints:
410
+
411
+ ### GET /config-id/challenge
412
+
413
+ Returns:
414
+
415
+ ```json
416
+ {
417
+ "token": "eyJ...",
418
+ "challenge_count": 16,
419
+ "salt_length": 32,
420
+ "difficulty": 20
421
+ }
422
+ ```
423
+
424
+ ### POST /config-id/redeem
425
+
426
+ Request body:
427
+
428
+ ```json
429
+ {
430
+ "token": "eyJ...",
431
+ "solutions": [12345, 67890, ...]
432
+ }
433
+ ```
434
+
435
+ Response:
436
+
437
+ ```json
438
+ {
439
+ "message": "proof_token_xyz"
440
+ }
441
+ ```
442
+
443
+ See [backend implementation guide](./docs/backend.md) for details.
444
+
445
+ ## Browser Support
446
+
447
+ | Browser | Version |
448
+ |---------|---------|
449
+ | Chrome | 76+ |
450
+ | Firefox | 79+ |
451
+ | Safari | 14.1+ |
452
+ | Edge | 79+ |
453
+
454
+ WebAssembly and ES2020 modules required.
455
+
456
+ ## Troubleshooting
457
+
458
+ **WASM not loading?**
459
+ - Check WASM paths in `wasm-glue` and `wasm-bin` attributes
460
+ - Ensure WASM served with `Content-Type: application/wasm`
461
+
462
+ **Events not firing?**
463
+ - Use `addEventListener()` (not `on*` attributes for Custom Events)
464
+ - Event names are `zaptcha-success`, not `zaptchaSuccess`
465
+
466
+ **Styling not applying?**
467
+ - CSS custom properties must be set on parent, not `:root`
468
+ - Use `!important` if conflicting with existing styles
469
+
470
+ ## Performance
471
+
472
+ - Challenge solving: 100-500ms (varies by difficulty)
473
+ - WASM bundle: 2-3MB uncompressed, gzipped to ~400KB
474
+ - Component size: ~8KB (with gzip)
475
+
476
+ ## Security
477
+
478
+ - No cookies stored
479
+ - No tracking pixels
480
+ - No external requests (except to your API)
481
+ - Proof-of-work verified on server
482
+ - Token valid for single use only
483
+
484
+ ## License
485
+
486
+ MIT © 2024 Your Organization
487
+
488
+ ## Contributing
489
+
490
+ See [CONTRIBUTING.md](CONTRIBUTING.md)
491
+
492
+ ## Support
493
+
494
+ - 📖 [Documentation](https://github.com/your-org/zero-captcha/wiki)
495
+ - 🐛 [Issue Tracker](https://github.com/your-org/zero-captcha/issues)
496
+ - 💬 [Discussions](https://github.com/your-org/zero-captcha/discussions)
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@zaptcha/widget",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./src/zaptcha-widget.js",
6
+ "types": "./src/zaptcha.d.ts",
7
+ "files": [
8
+ "src"
9
+ ],
10
+ "exports": {
11
+ ".": "./src/zaptcha-widget.js"
12
+ }
13
+ }
@@ -0,0 +1,485 @@
1
+ /**
2
+ * <zero-captcha> — CAPTCHA Web Component with WASM Proof-of-Work
3
+ *
4
+ * The worker is bundled inline — only this one file needs to be distributed.
5
+ * WASM paths are resolved automatically relative to this module's URL.
6
+ *
7
+ * Attributes:
8
+ * base-url="https://api.example.com" required — backend base URL
9
+ * config-id="AAAA" required — base64url-encoded u32
10
+ * wasm-glue="./pkg/zaptcha.js" optional — override WASM glue path
11
+ * wasm-bin="./pkg/zaptcha_bg.wasm" optional — override WASM binary path
12
+ *
13
+ * CSS custom properties (set on a parent element to theme the widget):
14
+ * --zaptcha-bg background color
15
+ * --zaptcha-border border color
16
+ * --zaptcha-border-focus focus ring color
17
+ * --zaptcha-text primary text color
18
+ * --zaptcha-text-muted muted / branding text color
19
+ * --zaptcha-accent spinner / interactive accent color
20
+ * --zaptcha-success checkmark fill color
21
+ * --zaptcha-shadow box shadow
22
+ * --zaptcha-radius border radius
23
+ *
24
+ * Events dispatched on host element:
25
+ * zaptcha-success -> detail: { token: string }
26
+ * zaptcha-fail -> detail: { reason: string }
27
+ * zaptcha-reset -> detail: {}
28
+ *
29
+ * Public methods:
30
+ * el.reset()
31
+ *
32
+ * Usage:
33
+ * <zero-captcha base-url="https://api.example.com" config-id="AAAA"></zero-captcha>
34
+ */
35
+
36
+ // ── Default WASM paths (relative to this module) ──────────────────────────
37
+
38
+ const _wasmGlue = new URL('./zaptcha.js', import.meta.url).href;
39
+ const _wasmBin = new URL('./zaptcha_bg.wasm', import.meta.url).href;
40
+
41
+ const WORKER_SRC = `
42
+ let wasmModule = null;
43
+ let wasmReady = false;
44
+ let wasmGlueUrl = null;
45
+ let wasmBinUrl = null;
46
+
47
+ async function initWasm() {
48
+ if (wasmReady) return;
49
+ if (!wasmGlueUrl) throw new Error("WASM paths not provided");
50
+ const mod = await import(wasmGlueUrl);
51
+ if (typeof mod.default === "function") await mod.default(wasmBinUrl);
52
+ wasmModule = mod;
53
+ wasmReady = true;
54
+ }
55
+
56
+ async function runSolve(baseUrl, configId) {
57
+ await initWasm();
58
+
59
+ const challengeRes = await fetch(baseUrl + "/" + configId + "/challenge");
60
+ if (!challengeRes.ok) throw new Error("Challenge request failed: HTTP " + challengeRes.status);
61
+
62
+ const { token, challenge_count, salt_length, difficulty } = await challengeRes.json();
63
+
64
+ self.__zaptchaProgressCb = (index, nonce) => {
65
+ self.postMessage({ type: "progress", index, nonce, total: challenge_count });
66
+ };
67
+
68
+ let solutions;
69
+ try {
70
+ solutions = wasmModule.solve_challenge(
71
+ token, difficulty, challenge_count, salt_length, self.__zaptchaProgressCb,
72
+ );
73
+ } finally {
74
+ delete self.__zaptchaProgressCb;
75
+ }
76
+
77
+ const redeemRes = await fetch(baseUrl + "/" + configId + "/redeem", {
78
+ method: "POST",
79
+ headers: { "Content-Type": "application/json" },
80
+ body: JSON.stringify({ token, solutions: Array.from(solutions) }),
81
+ });
82
+ if (!redeemRes.ok) throw new Error("Redeem request failed: HTTP " + redeemRes.status);
83
+
84
+ const { message } = await redeemRes.json();
85
+ if (!message) throw new Error("Server did not return a token");
86
+ return message;
87
+ }
88
+
89
+ self.onmessage = async function ({ data }) {
90
+ if (data.type === "init") {
91
+ wasmGlueUrl = data.wasmGlue;
92
+ wasmBinUrl = data.wasmBin;
93
+ try {
94
+ await initWasm();
95
+ self.postMessage({ type: "ready" });
96
+ } catch (err) {
97
+ self.postMessage({ type: "fail", reason: err.message });
98
+ }
99
+ return;
100
+ }
101
+
102
+ if (data.type === "solve") {
103
+ try {
104
+ const token = await runSolve(data.baseUrl, data.configId);
105
+ self.postMessage({ type: "success", token });
106
+ } catch (err) {
107
+ self.postMessage({ type: "fail", reason: err.message });
108
+ }
109
+ return;
110
+ }
111
+ };
112
+ `;
113
+
114
+ const STYLE = `
115
+ :host {
116
+ display: inline-block;
117
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
118
+ -webkit-font-smoothing: antialiased;
119
+ -moz-osx-font-smoothing: grayscale;
120
+
121
+ /*
122
+ * Map public --zaptcha-* custom properties to internal vars with
123
+ * sensible light-mode fallbacks.
124
+ */
125
+ --zap-bg: var(--zaptcha-bg, #ffffff);
126
+ --zap-border: var(--zaptcha-border, #e5e7eb);
127
+ --zap-border-hover: var(--zaptcha-border-hover, #d1d5db);
128
+ --zap-border-focus: var(--zaptcha-border-focus, #4f46e5);
129
+ --zap-text: var(--zaptcha-text, #374151);
130
+ --zap-text-muted: var(--zaptcha-text-muted, #9ca3af);
131
+ --zap-accent: var(--zaptcha-accent, #4f46e5);
132
+ --zap-success: var(--zaptcha-success, #10b981);
133
+ --zap-shadow: var(--zaptcha-shadow, 0 2px 6px rgba(0,0,0,0.04));
134
+ --zap-radius: var(--zaptcha-radius, 8px);
135
+ }
136
+
137
+ .widget {
138
+ background: var(--zap-bg);
139
+ border: 1px solid var(--zap-border);
140
+ border-radius: var(--zap-radius);
141
+ box-shadow: var(--zap-shadow);
142
+ width: 210px; /* Слегка шире для баланса, но все еще очень компактно */
143
+ box-sizing: border-box;
144
+ transition: box-shadow 0.2s ease, border-color 0.2s ease;
145
+ }
146
+
147
+ .widget:hover {
148
+ box-shadow: 0 4px 12px rgba(0,0,0,0.06);
149
+ }
150
+
151
+ .row {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 10px;
155
+ padding: 0 12px;
156
+ height: 48px;
157
+ cursor: pointer;
158
+ user-select: none;
159
+ box-sizing: border-box;
160
+ border-radius: var(--zap-radius);
161
+ outline: none;
162
+ }
163
+
164
+ .row:focus-visible {
165
+ box-shadow: 0 0 0 2px var(--zap-bg), 0 0 0 4px var(--zap-border-focus);
166
+ }
167
+
168
+ .row[aria-checked="true"] {
169
+ cursor: default;
170
+ }
171
+
172
+ /* Checkbox */
173
+ .box {
174
+ width: 22px;
175
+ height: 22px;
176
+ flex-shrink: 0;
177
+ position: relative;
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ }
182
+
183
+ .box-border {
184
+ position: absolute;
185
+ inset: 0;
186
+ border: 1.5px solid var(--zap-border);
187
+ border-radius: 4px;
188
+ background: var(--zap-bg);
189
+ transition: border-color 0.2s ease, background-color 0.2s ease;
190
+ }
191
+
192
+ .row:hover:not([aria-checked="true"]) .box-border {
193
+ border-color: var(--zap-border-hover);
194
+ }
195
+
196
+ .box.done .box-border {
197
+ background: var(--zap-success);
198
+ border-color: var(--zap-success);
199
+ }
200
+
201
+ .box-tick { display: none; position: relative; z-index: 1; }
202
+ .box.done .box-tick { display: block; animation: zap-pop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
203
+
204
+ @keyframes zap-pop {
205
+ 0% { transform: scale(0.5); opacity: 0; }
206
+ 100% { transform: scale(1); opacity: 1; }
207
+ }
208
+
209
+ /* Spinner */
210
+ .box-spinner { display: none; position: absolute; inset: 0; }
211
+ .box.spinning .box-border { border-color: transparent; background: transparent; }
212
+ .box.spinning .box-spinner {
213
+ display: flex;
214
+ align-items: center;
215
+ justify-content: center;
216
+ }
217
+ .box-spinner::after {
218
+ content: '';
219
+ display: block;
220
+ width: 18px;
221
+ height: 18px;
222
+ border: 2px solid var(--zap-border);
223
+ border-top-color: var(--zap-accent);
224
+ border-radius: 50%;
225
+ animation: zap-spin 0.6s linear infinite;
226
+ }
227
+ @keyframes zap-spin { to { transform: rotate(360deg); } }
228
+
229
+ /* Label */
230
+ .label {
231
+ color: var(--zap-text);
232
+ font-size: 13.5px;
233
+ font-weight: 500;
234
+ flex: 1;
235
+ white-space: nowrap;
236
+ overflow: hidden;
237
+ text-overflow: ellipsis;
238
+ }
239
+
240
+ /* Branding */
241
+ .branding {
242
+ display: flex;
243
+ flex-direction: column;
244
+ align-items: flex-end;
245
+ justify-content: center;
246
+ flex-shrink: 0;
247
+ gap: 1px;
248
+ }
249
+ .branding strong {
250
+ font-size: 9px;
251
+ font-weight: 700;
252
+ letter-spacing: 0.05em;
253
+ text-transform: uppercase;
254
+ color: var(--zap-text-muted);
255
+ }
256
+ .branding a {
257
+ font-size: 9px;
258
+ color: var(--zap-text-muted);
259
+ text-decoration: none;
260
+ transition: color 0.2s;
261
+ }
262
+ .branding a:hover {
263
+ color: var(--zap-text);
264
+ }
265
+ `;
266
+
267
+ class ZeroCaptcha extends HTMLElement {
268
+
269
+ static get observedAttributes() { return []; }
270
+
271
+ constructor() {
272
+ super();
273
+ this._shadow = this.attachShadow({ mode: "open" });
274
+ this._worker = null;
275
+ this._blobUrl = null;
276
+ /** @type {"idle"|"solving"|"done"|"error"} */
277
+ this._state = "idle";
278
+ this._progress = { done: 0, total: 1 };
279
+ this._render();
280
+ }
281
+
282
+ connectedCallback() {
283
+ this._bindEvents();
284
+ this._startWorker();
285
+ }
286
+
287
+ disconnectedCallback() {
288
+ this._destroyWorker();
289
+ }
290
+
291
+ reset() {
292
+ this._destroyWorker();
293
+ this._state = "idle";
294
+ this._progress = { done: 0, total: 1 };
295
+ this._render();
296
+ this._bindEvents();
297
+ this._startWorker();
298
+ this.dispatchEvent(new CustomEvent("zaptcha-reset", { bubbles: true, detail: {} }));
299
+ }
300
+
301
+ _startWorker() {
302
+ try {
303
+ const externalUrl = this.getAttribute("worker-url");
304
+ if (externalUrl) {
305
+ this._worker = new Worker(externalUrl, { type: "module" });
306
+ } else {
307
+ const blob = new Blob([WORKER_SRC], { type: "text/javascript" });
308
+ this._blobUrl = URL.createObjectURL(blob);
309
+ this._worker = new Worker(this._blobUrl, { type: "module" });
310
+ }
311
+
312
+ this._worker.onmessage = (e) => this._onWorkerMsg(e.data);
313
+ this._worker.onerror = (e) => this._onFail(`Worker error: ${e.message}`);
314
+
315
+ const wasmGlue = this.getAttribute("wasm-glue") ?? _wasmGlue;
316
+ const wasmBin = this.getAttribute("wasm-bin") ?? _wasmBin;
317
+
318
+ this._worker.postMessage({ type: "init", wasmGlue, wasmBin });
319
+ } catch (err) {
320
+ this._worker = null;
321
+ }
322
+ }
323
+
324
+ _destroyWorker() {
325
+ this._worker?.terminate();
326
+ this._worker = null;
327
+ if (this._blobUrl) {
328
+ URL.revokeObjectURL(this._blobUrl);
329
+ this._blobUrl = null;
330
+ }
331
+ }
332
+
333
+ _onWorkerMsg(msg) {
334
+ switch (msg.type) {
335
+ case "ready":
336
+ break;
337
+
338
+ case "progress":
339
+ this._progress.done = (msg.index ?? 0) + 1;
340
+ this._progress.total = msg.total ?? 1;
341
+ this.dispatchEvent(new CustomEvent("zaptcha-progress", {
342
+ bubbles: true,
343
+ composed: true,
344
+ detail: {
345
+ index: msg.index,
346
+ nonce: msg.nonce,
347
+ total: msg.total,
348
+ pct: Math.round(((msg.index ?? 0) + 1) / (msg.total ?? 1) * 100),
349
+ },
350
+ }));
351
+ break;
352
+
353
+ case "success":
354
+ this._state = "done";
355
+ this._render();
356
+ this.dispatchEvent(new CustomEvent("zaptcha-success", {
357
+ bubbles: true,
358
+ composed: true,
359
+ detail: { token: msg.token },
360
+ }));
361
+ break;
362
+
363
+ case "fail":
364
+ this._onFail(msg.reason || "Unknown error");
365
+ break;
366
+ }
367
+ }
368
+
369
+
370
+ _render() {
371
+ const solving = this._state === "solving";
372
+ const done = this._state === "done";
373
+
374
+ const label = done ? "Verified" :
375
+ solving ? "Verifying\u2026" :
376
+ "I'm not a robot";
377
+
378
+ this._shadow.innerHTML = `
379
+ <style>${STYLE}</style>
380
+ <div class="widget" part="widget">
381
+ <div class="row"
382
+ id="row"
383
+ tabindex="${done ? -1 : 0}"
384
+ role="checkbox"
385
+ aria-checked="${done}"
386
+ aria-label="${label}">
387
+
388
+ <div class="box ${solving ? "spinning" : done ? "done" : ""}" id="box" part="checkbox">
389
+ <div class="box-border"></div>
390
+ <div class="box-spinner"></div>
391
+ <svg class="box-tick" width="12" height="9" viewBox="0 0 12 9" fill="none" aria-hidden="true">
392
+ <polyline points="1.5,4.5 4.5,7.5 10.5,1.5"
393
+ stroke="white" stroke-width="2"
394
+ stroke-linecap="round" stroke-linejoin="round"/>
395
+ </svg>
396
+ </div>
397
+
398
+ <span class="label" part="label">${label}</span>
399
+
400
+ <div class="branding" part="branding">
401
+ <strong>Zaptcha</strong>
402
+ <a href="#" tabindex="-1">Privacy</a>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ `;
407
+ }
408
+
409
+ // ── Event binding ───────────────────────────────────────────────────────
410
+
411
+ _bindEvents() {
412
+ const row = this._shadow.getElementById("row");
413
+ if (!row) return;
414
+ row.addEventListener("click", () => this._onRowClick());
415
+ row.addEventListener("keydown", (e) => {
416
+ if (e.key === " " || e.key === "Enter") {
417
+ e.preventDefault();
418
+ this._onRowClick();
419
+ }
420
+ });
421
+ }
422
+
423
+ _onRowClick() {
424
+ if (this._state !== "idle") return;
425
+ this._startSolve();
426
+ }
427
+
428
+ // ── Solving flow ────────────────────────────────────────────────────────
429
+
430
+ _startSolve() {
431
+ this._state = "solving";
432
+ this._progress = { done: 0, total: 1 };
433
+ this._render();
434
+
435
+ if (!this._worker) {
436
+ this._onFail("Worker unavailable");
437
+ return;
438
+ }
439
+
440
+ const baseUrl = this.getAttribute("base-url") ?? "";
441
+ const configId = this.getAttribute("config-id") ?? "";
442
+
443
+ if (!baseUrl || !configId) {
444
+ this._onFail("Attributes base-url and config-id are required");
445
+ return;
446
+ }
447
+
448
+ this._worker.postMessage({ type: "solve", baseUrl, configId });
449
+ }
450
+
451
+ _onFail(reason) {
452
+ this._state = "error";
453
+ this._render();
454
+
455
+ this.dispatchEvent(new CustomEvent("zaptcha-fail", {
456
+ bubbles: true,
457
+ composed: true,
458
+ detail: { reason },
459
+ }));
460
+
461
+ setTimeout(() => this.reset(), 3000);
462
+ }
463
+ }
464
+
465
+ customElements.define("zero-captcha", ZeroCaptcha);
466
+ export default ZeroCaptcha;
467
+ export { ZeroCaptcha };
468
+
469
+ // ── Standalone worker factory ─────────────────────────────────────────────
470
+
471
+ export function createWorker({ wasmGlue = _wasmGlue, wasmBin = _wasmBin } = {}) {
472
+ const blob = new Blob([WORKER_SRC], { type: "text/javascript" });
473
+ const blobUrl = URL.createObjectURL(blob);
474
+ const worker = new Worker(blobUrl, { type: "module" });
475
+
476
+ worker.addEventListener("message", function onReady(e) {
477
+ if (e.data?.type === "ready" || e.data?.type === "fail") {
478
+ URL.revokeObjectURL(blobUrl);
479
+ worker.removeEventListener("message", onReady);
480
+ }
481
+ });
482
+
483
+ worker.postMessage({ type: "init", wasmGlue, wasmBin });
484
+ return worker;
485
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * zaptcha.d.ts — TypeScript type declarations for <zero-captcha>
3
+ *
4
+ * Usage (module):
5
+ * import type { ZeroCaptcha, ZaptchaSuccessEvent, ZaptchaFailEvent } from "./zaptcha";
6
+ *
7
+ * Usage (global augmentation, add to your project's *.d.ts):
8
+ * /// <reference types="./zaptcha" />
9
+ */
10
+
11
+ // ── Event payload types ───────────────────────────────────────────────────
12
+
13
+ /** Fired when PoW solving succeeds and the server returns a signed token. */
14
+ export interface ZaptchaSuccessDetail {
15
+ /** Signed token returned by the backend after successful PoW redemption. */
16
+ token: string;
17
+ }
18
+
19
+ /** Fired when solving or network communication fails. */
20
+ export interface ZaptchaFailDetail {
21
+ /** Human-readable error description. */
22
+ reason: string;
23
+ }
24
+
25
+ /** Fired on each solved PoW challenge. */
26
+ export interface ZaptchaProgressDetail {
27
+ index: number;
28
+ nonce: number;
29
+ total: number;
30
+ /** 0–100 */
31
+ pct: number;
32
+ }
33
+
34
+ /** Fired when the widget is reset to its idle state. */
35
+ export type ZaptchaResetDetail = Record<string, never>;
36
+
37
+ export type ZaptchaSuccessEvent = CustomEvent<ZaptchaSuccessDetail>;
38
+ export type ZaptchaFailEvent = CustomEvent<ZaptchaFailDetail>;
39
+ export type ZaptchaProgressEvent = CustomEvent<ZaptchaProgressDetail>;
40
+ export type ZaptchaResetEvent = CustomEvent<ZaptchaResetDetail>;
41
+
42
+ // ── Element class ─────────────────────────────────────────────────────────
43
+
44
+ export declare class ZeroCaptcha extends HTMLElement {
45
+ /**
46
+ * Backend base URL.
47
+ * Maps to the `base-url` attribute.
48
+ */
49
+ baseUrl: string;
50
+
51
+ /**
52
+ * Base64url-encoded config identifier (u32).
53
+ * Maps to the `config-id` attribute.
54
+ */
55
+ configId: string;
56
+
57
+ /**
58
+ * Color theme.
59
+ * Maps to the `theme` attribute.
60
+ * @default "light"
61
+ */
62
+ theme: "light" | "dark";
63
+
64
+ /**
65
+ * URL of the Web Worker script.
66
+ * Maps to the `worker-url` attribute.
67
+ * @default "./zaptcha-worker.js"
68
+ */
69
+ workerUrl: string;
70
+
71
+ /**
72
+ * Reset the widget to idle state, terminate any in-progress solving,
73
+ * and fire a `zaptcha-reset` event.
74
+ */
75
+ reset(): void;
76
+
77
+ // ── Standard HTMLElement event overloads ────────────────────────────────
78
+
79
+ addEventListener(
80
+ type: "zaptcha-progress",
81
+ listener: (ev: ZaptchaProgressEvent) => void,
82
+ options?: boolean | AddEventListenerOptions,
83
+ ): void;
84
+ addEventListener(
85
+ type: "zaptcha-success",
86
+ listener: (ev: ZaptchaSuccessEvent) => void,
87
+ options?: boolean | AddEventListenerOptions,
88
+ ): void;
89
+ addEventListener(
90
+ type: "zaptcha-fail",
91
+ listener: (ev: ZaptchaFailEvent) => void,
92
+ options?: boolean | AddEventListenerOptions,
93
+ ): void;
94
+ addEventListener(
95
+ type: "zaptcha-reset",
96
+ listener: (ev: ZaptchaResetEvent) => void,
97
+ options?: boolean | AddEventListenerOptions,
98
+ ): void;
99
+ addEventListener(
100
+ type: string,
101
+ listener: EventListenerOrEventListenerObject,
102
+ options?: boolean | AddEventListenerOptions,
103
+ ): void;
104
+ }
105
+
106
+ // ── Global HTMLElementTagNameMap augmentation ─────────────────────────────
107
+ // Enables typed querySelector / createElement in TypeScript projects.
108
+
109
+ declare global {
110
+ interface HTMLElementTagNameMap {
111
+ "zero-captcha": ZeroCaptcha;
112
+ }
113
+
114
+ interface HTMLElementEventMap {
115
+ "zaptcha-progress": ZaptchaProgressEvent;
116
+ "zaptcha-success": ZaptchaSuccessEvent;
117
+ "zaptcha-fail": ZaptchaFailEvent;
118
+ "zaptcha-reset": ZaptchaResetEvent;
119
+ }
120
+ }
121
+
122
+ export default ZeroCaptcha;
123
+
124
+ // ── Standalone worker factory ─────────────────────────────────────────────
125
+
126
+ export interface CreateWorkerOptions {
127
+ /** Absolute URL to the WASM JS glue file. Defaults to the bundled path. */
128
+ wasmGlue?: string;
129
+ /** Absolute URL to the WASM binary. Defaults to the bundled path. */
130
+ wasmBin?: string;
131
+ }
132
+
133
+ /**
134
+ * Create a standalone Zaptcha worker without the widget UI.
135
+ * The worker is pre-warmed and ready to receive `"solve"` messages.
136
+ *
137
+ * @example
138
+ * const worker = createWorker();
139
+ * worker.onmessage = ({ data }) => {
140
+ * if (data.type === 'success') console.log(data.token);
141
+ * };
142
+ * worker.postMessage({ type: 'solve', baseUrl: 'https://api.example.com', configId: 'AAAA' });
143
+ * worker.terminate();
144
+ */
145
+ export function createWorker(options?: CreateWorkerOptions): Worker;
package/src/zaptcha.js ADDED
@@ -0,0 +1,249 @@
1
+ /* @ts-self-types="./zaptcha.d.ts" */
2
+
3
+ /**
4
+ * @param {string} token
5
+ * @param {number} difficulty
6
+ * @param {number} challenge_count
7
+ * @param {number} salt_length
8
+ * @param {Function} progress_callback
9
+ * @returns {Uint32Array}
10
+ */
11
+ export function solve_challenge(token, difficulty, challenge_count, salt_length, progress_callback) {
12
+ const ptr0 = passStringToWasm0(token, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
13
+ const len0 = WASM_VECTOR_LEN;
14
+ const ret = wasm.solve_challenge(ptr0, len0, difficulty, challenge_count, salt_length, progress_callback);
15
+ var v2 = getArrayU32FromWasm0(ret[0], ret[1]).slice();
16
+ wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
17
+ return v2;
18
+ }
19
+
20
+ function __wbg_get_imports() {
21
+ const import0 = {
22
+ __proto__: null,
23
+ __wbg___wbindgen_throw_6ddd609b62940d55: function(arg0, arg1) {
24
+ throw new Error(getStringFromWasm0(arg0, arg1));
25
+ },
26
+ __wbg_call_dcc2662fa17a72cf: function() { return handleError(function (arg0, arg1, arg2, arg3) {
27
+ const ret = arg0.call(arg1, arg2, arg3);
28
+ return ret;
29
+ }, arguments); },
30
+ __wbindgen_cast_0000000000000001: function(arg0) {
31
+ // Cast intrinsic for `F64 -> Externref`.
32
+ const ret = arg0;
33
+ return ret;
34
+ },
35
+ __wbindgen_init_externref_table: function() {
36
+ const table = wasm.__wbindgen_externrefs;
37
+ const offset = table.grow(4);
38
+ table.set(0, undefined);
39
+ table.set(offset + 0, undefined);
40
+ table.set(offset + 1, null);
41
+ table.set(offset + 2, true);
42
+ table.set(offset + 3, false);
43
+ },
44
+ };
45
+ return {
46
+ __proto__: null,
47
+ "./zaptcha_bg.js": import0,
48
+ };
49
+ }
50
+
51
+ function addToExternrefTable0(obj) {
52
+ const idx = wasm.__externref_table_alloc();
53
+ wasm.__wbindgen_externrefs.set(idx, obj);
54
+ return idx;
55
+ }
56
+
57
+ function getArrayU32FromWasm0(ptr, len) {
58
+ ptr = ptr >>> 0;
59
+ return getUint32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len);
60
+ }
61
+
62
+ function getStringFromWasm0(ptr, len) {
63
+ ptr = ptr >>> 0;
64
+ return decodeText(ptr, len);
65
+ }
66
+
67
+ let cachedUint32ArrayMemory0 = null;
68
+ function getUint32ArrayMemory0() {
69
+ if (cachedUint32ArrayMemory0 === null || cachedUint32ArrayMemory0.byteLength === 0) {
70
+ cachedUint32ArrayMemory0 = new Uint32Array(wasm.memory.buffer);
71
+ }
72
+ return cachedUint32ArrayMemory0;
73
+ }
74
+
75
+ let cachedUint8ArrayMemory0 = null;
76
+ function getUint8ArrayMemory0() {
77
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
78
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
79
+ }
80
+ return cachedUint8ArrayMemory0;
81
+ }
82
+
83
+ function handleError(f, args) {
84
+ try {
85
+ return f.apply(this, args);
86
+ } catch (e) {
87
+ const idx = addToExternrefTable0(e);
88
+ wasm.__wbindgen_exn_store(idx);
89
+ }
90
+ }
91
+
92
+ function passStringToWasm0(arg, malloc, realloc) {
93
+ if (realloc === undefined) {
94
+ const buf = cachedTextEncoder.encode(arg);
95
+ const ptr = malloc(buf.length, 1) >>> 0;
96
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
97
+ WASM_VECTOR_LEN = buf.length;
98
+ return ptr;
99
+ }
100
+
101
+ let len = arg.length;
102
+ let ptr = malloc(len, 1) >>> 0;
103
+
104
+ const mem = getUint8ArrayMemory0();
105
+
106
+ let offset = 0;
107
+
108
+ for (; offset < len; offset++) {
109
+ const code = arg.charCodeAt(offset);
110
+ if (code > 0x7F) break;
111
+ mem[ptr + offset] = code;
112
+ }
113
+ if (offset !== len) {
114
+ if (offset !== 0) {
115
+ arg = arg.slice(offset);
116
+ }
117
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
118
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
119
+ const ret = cachedTextEncoder.encodeInto(arg, view);
120
+
121
+ offset += ret.written;
122
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
123
+ }
124
+
125
+ WASM_VECTOR_LEN = offset;
126
+ return ptr;
127
+ }
128
+
129
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
130
+ cachedTextDecoder.decode();
131
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
132
+ let numBytesDecoded = 0;
133
+ function decodeText(ptr, len) {
134
+ numBytesDecoded += len;
135
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
136
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
137
+ cachedTextDecoder.decode();
138
+ numBytesDecoded = len;
139
+ }
140
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
141
+ }
142
+
143
+ const cachedTextEncoder = new TextEncoder();
144
+
145
+ if (!('encodeInto' in cachedTextEncoder)) {
146
+ cachedTextEncoder.encodeInto = function (arg, view) {
147
+ const buf = cachedTextEncoder.encode(arg);
148
+ view.set(buf);
149
+ return {
150
+ read: arg.length,
151
+ written: buf.length
152
+ };
153
+ };
154
+ }
155
+
156
+ let WASM_VECTOR_LEN = 0;
157
+
158
+ let wasmModule, wasm;
159
+ function __wbg_finalize_init(instance, module) {
160
+ wasm = instance.exports;
161
+ wasmModule = module;
162
+ cachedUint32ArrayMemory0 = null;
163
+ cachedUint8ArrayMemory0 = null;
164
+ wasm.__wbindgen_start();
165
+ return wasm;
166
+ }
167
+
168
+ async function __wbg_load(module, imports) {
169
+ if (typeof Response === 'function' && module instanceof Response) {
170
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
171
+ try {
172
+ return await WebAssembly.instantiateStreaming(module, imports);
173
+ } catch (e) {
174
+ const validResponse = module.ok && expectedResponseType(module.type);
175
+
176
+ if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
177
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
178
+
179
+ } else { throw e; }
180
+ }
181
+ }
182
+
183
+ const bytes = await module.arrayBuffer();
184
+ return await WebAssembly.instantiate(bytes, imports);
185
+ } else {
186
+ const instance = await WebAssembly.instantiate(module, imports);
187
+
188
+ if (instance instanceof WebAssembly.Instance) {
189
+ return { instance, module };
190
+ } else {
191
+ return instance;
192
+ }
193
+ }
194
+
195
+ function expectedResponseType(type) {
196
+ switch (type) {
197
+ case 'basic': case 'cors': case 'default': return true;
198
+ }
199
+ return false;
200
+ }
201
+ }
202
+
203
+ function initSync(module) {
204
+ if (wasm !== undefined) return wasm;
205
+
206
+
207
+ if (module !== undefined) {
208
+ if (Object.getPrototypeOf(module) === Object.prototype) {
209
+ ({module} = module)
210
+ } else {
211
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
212
+ }
213
+ }
214
+
215
+ const imports = __wbg_get_imports();
216
+ if (!(module instanceof WebAssembly.Module)) {
217
+ module = new WebAssembly.Module(module);
218
+ }
219
+ const instance = new WebAssembly.Instance(module, imports);
220
+ return __wbg_finalize_init(instance, module);
221
+ }
222
+
223
+ async function __wbg_init(module_or_path) {
224
+ if (wasm !== undefined) return wasm;
225
+
226
+
227
+ if (module_or_path !== undefined) {
228
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
229
+ ({module_or_path} = module_or_path)
230
+ } else {
231
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
232
+ }
233
+ }
234
+
235
+ if (module_or_path === undefined) {
236
+ module_or_path = new URL('zaptcha_bg.wasm', import.meta.url);
237
+ }
238
+ const imports = __wbg_get_imports();
239
+
240
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
241
+ module_or_path = fetch(module_or_path);
242
+ }
243
+
244
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
245
+
246
+ return __wbg_finalize_init(instance, module);
247
+ }
248
+
249
+ export { initSync, __wbg_init as default };
Binary file