@maggidev/captchashield 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/cookies.ts","../src/renderer.ts","../src/validation.ts","../src/turnstile.ts","../src/network.ts","../src/verification.ts","../src/shield.ts"],"sourcesContent":["export {\n createCaptchaShield,\n DEFAULT_COOKIE_NAME,\n DEFAULT_SCRIPT_URL,\n} from './shield';\n\nexport {\n hasCookie,\n setCookie,\n clearCookie,\n} from './cookies';\n\nexport { CaptchaShieldError } from './errors';\n\nexport type {\n ShieldConfig,\n ShieldController,\n ShieldOpenResult,\n ShieldRenderer,\n RendererContext,\n RendererHandle,\n CookieOptions,\n ModalOptions,\n ModalCopyOptions,\n ModalStyleOptions,\n VerifyOptions,\n StatusOptions,\n IntegrityOptions,\n ResolvedShieldConfig,\n} from './types';\n","export class CaptchaShieldError extends Error {\n constructor(message: string, public originalError?: unknown) {\n super(`[CaptchaShield] ${message}`);\n this.name = 'CaptchaShieldError';\n\n // Maintain proper stack trace for where our error was thrown (only available on V8)\n type ConstructorLike = abstract new (...args: unknown[]) => unknown;\n const maybeCaptureStackTrace = (\n Error as unknown as { captureStackTrace?: (target: object, ctor?: ConstructorLike) => void }\n ).captureStackTrace;\n maybeCaptureStackTrace?.(this, CaptchaShieldError as unknown as ConstructorLike);\n }\n}\n","import { CaptchaShieldError } from './errors';\nimport { ResolvedCookieOptions } from './types';\n\n// RFC 6265 §4.1.1: cookie-name must be a US-ASCII \"token\" —\n// printable characters excluding control chars and the listed separators.\nconst COOKIE_NAME_RE = /^[!#$%&'*+\\-.0-9A-Z^_`a-z|~]+$/;\n\nfunction assertValidCookieName(name: string): void {\n if (!name || !COOKIE_NAME_RE.test(name)) {\n throw new CaptchaShieldError(\n `Invalid cookie.name \"${name}\": must be a valid RFC 6265 token (printable ASCII, no separators or control characters).`\n );\n }\n}\n\n// RFC 6265 §4.1.1: cookie attribute values must not contain semicolons.\nfunction assertValidCookieAttributeValue(value: string, attr: string): void {\n if (value.includes(';')) {\n throw new CaptchaShieldError(\n `Invalid cookie.${attr} \"${value}\": must not contain semicolons.`\n );\n }\n}\n\nexport function hasCookie(name: string): boolean {\n if (typeof document === 'undefined') return false;\n return document.cookie.split(';').some((item) => item.trim().startsWith(`${name}=`));\n}\n\nexport function setCookie(options: ResolvedCookieOptions, value: string) {\n assertValidCookieName(options.name);\n if (options.domain) assertValidCookieAttributeValue(options.domain, 'domain');\n assertValidCookieAttributeValue(options.path, 'path');\n\n const attributes = [\n `path=${options.path}`,\n `max-age=${options.maxAgeSeconds}`,\n options.domain ? `domain=${options.domain}` : '',\n options.secure ? 'secure' : '',\n options.sameSite ? `samesite=${options.sameSite}` : '',\n ]\n .filter(Boolean)\n .join('; ');\n\n document.cookie = `${options.name}=${encodeURIComponent(value)}; ${attributes}`;\n}\n\nexport function clearCookie(options: ResolvedCookieOptions) {\n assertValidCookieName(options.name);\n if (options.domain) assertValidCookieAttributeValue(options.domain, 'domain');\n assertValidCookieAttributeValue(options.path, 'path');\n\n const attributes = [\n `expires=Thu, 01 Jan 1970 00:00:00 GMT`,\n `path=${options.path}`,\n options.domain ? `domain=${options.domain}` : '',\n options.secure ? 'secure' : '',\n options.sameSite ? `samesite=${options.sameSite}` : '',\n ]\n .filter(Boolean)\n .join('; ');\n\n document.cookie = `${options.name}=; ${attributes}`;\n}\n\nexport function deriveCookieName(baseName: string, options: ResolvedCookieOptions): string {\n if (!options.useScopePrefix) return baseName;\n const scope = options.scopeId ?? deriveScopeId();\n return scope ? `${baseName}_${scope}` : baseName;\n}\n\nfunction deriveScopeId(): string | undefined {\n if (typeof window === 'undefined') return undefined;\n try {\n return window.location.hostname.replace(/\\./g, '_');\n } catch {\n return undefined;\n }\n}\n","import { ResolvedShieldConfig, RendererHandle } from './types';\n\nexport interface RenderParams {\n challengeContainer: HTMLElement;\n config: ResolvedShieldConfig;\n close: () => void;\n}\n\nexport function renderDefaultModal({ challengeContainer, config, close }: RenderParams): RendererHandle {\n const overlay = document.createElement('div');\n const panel = document.createElement('div');\n const header = document.createElement('div');\n const title = document.createElement('h2');\n const closeButton = document.createElement('button');\n const body = document.createElement('p');\n const helper = document.createElement('p');\n\n overlay.setAttribute('role', 'presentation');\n overlay.className = config.modal.styles.overlayClass;\n overlay.setAttribute('data-captcha-shield', 'overlay');\n\n panel.className = config.modal.styles.panelClass;\n panel.setAttribute('role', 'dialog');\n panel.setAttribute('aria-modal', 'true');\n panel.setAttribute('aria-label', config.modal.ariaLabel);\n panel.setAttribute('data-captcha-shield', 'panel');\n\n header.className = 'captcha-shield__header';\n\n title.className = config.modal.styles.titleClass;\n title.textContent = config.modal.copy.title;\n\n closeButton.type = 'button';\n closeButton.className = 'captcha-shield__close';\n closeButton.setAttribute('aria-label', 'Close verification dialog');\n closeButton.textContent = 'Close';\n closeButton.addEventListener('click', close);\n\n body.className = config.modal.styles.bodyClass;\n body.textContent = config.modal.copy.body;\n\n helper.className = config.modal.styles.helperClass;\n helper.textContent = config.modal.copy.helperText;\n\n header.appendChild(title);\n header.appendChild(closeButton);\n panel.appendChild(header);\n panel.appendChild(body);\n panel.appendChild(challengeContainer);\n panel.appendChild(helper);\n overlay.appendChild(panel);\n\n let injectedStyle: HTMLStyleElement | null = null;\n if (config.modal.injectDefaultStyle) {\n injectedStyle = injectStyle(defaultStyleSheet(config.modal.styles.customCss));\n } else if (config.modal.styles.customCss.trim().length > 0) {\n injectedStyle = injectStyle(config.modal.styles.customCss);\n }\n\n return {\n root: overlay,\n destroy: () => {\n overlay.remove();\n injectedStyle?.remove();\n },\n };\n}\n\nfunction injectStyle(css: string): HTMLStyleElement | null {\n if (!css.trim()) return null;\n const style = document.createElement('style');\n style.setAttribute('data-captcha-shield-style', 'true');\n style.textContent = css;\n document.head.appendChild(style);\n return style;\n}\n\nfunction defaultStyleSheet(customCss: string): string {\n const base = `\n.captcha-shield__overlay { position: fixed; inset: 0; background: rgba(15, 23, 42, 0.48); display: flex; align-items: center; justify-content: center; padding: 20px; z-index: 9999; }\n.captcha-shield__panel { width: min(480px, 100%); background: #ffffff; color: #111827; border: 1px solid #d4d4d8; border-radius: 10px; box-shadow: 0 6px 24px rgba(15, 23, 42, 0.12); padding: 20px; font-family: inherit; display: flex; flex-direction: column; gap: 16px; }\n.captcha-shield__header { display: flex; align-items: start; justify-content: space-between; gap: 16px; }\n.captcha-shield__title { margin: 0; font-size: 1.125rem; line-height: 1.35; font-weight: 650; }\n.captcha-shield__close { appearance: none; border: 1px solid #d4d4d8; background: #ffffff; color: #374151; border-radius: 8px; padding: 7px 10px; font: inherit; font-size: 0.875rem; font-weight: 600; cursor: pointer; }\n.captcha-shield__close:hover { background: #f4f4f5; }\n.captcha-shield__body { margin: 0; line-height: 1.6; color: #1f2937; }\n.captcha-shield__helper { margin: 0; font-size: 0.9375rem; color: #52525b; }\n[data-captcha-shield=\"challenge\"] { min-height: 70px; display: flex; align-items: center; justify-content: center; }\n`;\n return `${base}${customCss ?? ''}`;\n}\n","import { CaptchaShieldError } from './errors';\n\nconst ABSOLUTE_URL_PATTERN = /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/;\nconst LOCALHOST_HOSTNAMES = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);\n\nexport function validateRequestEndpoint(endpoint: string, fieldName: string): string {\n const trimmed = endpoint.trim();\n if (!trimmed) {\n throw new CaptchaShieldError(`Configuration field \"${fieldName}\" cannot be empty.`);\n }\n\n if (trimmed.startsWith('//')) {\n throw new CaptchaShieldError(`Configuration field \"${fieldName}\" must not use protocol-relative URLs.`);\n }\n\n if (!ABSOLUTE_URL_PATTERN.test(trimmed)) {\n return trimmed;\n }\n\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n throw new CaptchaShieldError(`Configuration field \"${fieldName}\" must be a valid URL.`);\n }\n\n if (url.protocol === 'https:') {\n return url.toString();\n }\n\n if (url.protocol === 'http:' && LOCALHOST_HOSTNAMES.has(url.hostname)) {\n return url.toString();\n }\n\n throw new CaptchaShieldError(\n `Configuration field \"${fieldName}\" must use HTTPS. Plain HTTP is only allowed for localhost development.`\n );\n}\n\nexport function validateTurnstileScriptUrl(scriptUrl: string): string {\n let url: URL;\n try {\n url = new URL(scriptUrl);\n } catch {\n throw new CaptchaShieldError('turnstileScriptUrl must be a valid absolute URL.');\n }\n\n if (url.protocol !== 'https:') {\n throw new CaptchaShieldError('turnstileScriptUrl must use HTTPS.');\n }\n\n if (url.hostname !== 'challenges.cloudflare.com' || !url.pathname.startsWith('/turnstile/')) {\n throw new CaptchaShieldError(\n 'turnstileScriptUrl must point to the official Cloudflare Turnstile host.'\n );\n }\n\n return url.toString();\n}\n","import { ResolvedIntegrityOptions, TurnstileGlobal } from './types';\nimport { CaptchaShieldError } from './errors';\nimport { validateTurnstileScriptUrl } from './validation';\n\n// Keyed by script URL so instances with different URLs each get their own load.\nconst turnstileLoaders = new Map<string, Promise<TurnstileGlobal>>();\n\nexport function ensureTurnstile(scriptUrl: string, integrity: ResolvedIntegrityOptions): Promise<TurnstileGlobal> {\n requireDom();\n if (window.turnstile) {\n assertTurnstile(integrity);\n return Promise.resolve(window.turnstile);\n }\n\n const existing = turnstileLoaders.get(scriptUrl);\n if (existing) return existing;\n\n const loader = loadTurnstileScript(scriptUrl, integrity).catch((err) => {\n turnstileLoaders.delete(scriptUrl);\n throw err;\n });\n\n turnstileLoaders.set(scriptUrl, loader);\n return loader;\n}\n\nfunction loadTurnstileScript(scriptUrl: string, integrity: ResolvedIntegrityOptions): Promise<TurnstileGlobal> {\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = validateTurnstileScriptUrl(scriptUrl);\n script.async = true;\n if (integrity.scriptIntegrity) {\n script.integrity = integrity.scriptIntegrity;\n script.crossOrigin = 'anonymous';\n }\n script.onload = () => {\n if (window.turnstile) {\n try {\n assertTurnstile(integrity);\n resolve(window.turnstile);\n } catch (err) {\n reject(err);\n }\n } else {\n reject(new CaptchaShieldError('Turnstile script loaded but \"window.turnstile\" is missing.'));\n }\n };\n script.onerror = () => reject(new CaptchaShieldError(`Failed to load Turnstile script from ${scriptUrl}`));\n document.head.appendChild(script);\n });\n}\n\nfunction assertTurnstile(integrity: ResolvedIntegrityOptions) {\n if (!integrity.verifyTurnstileGlobal) return;\n const t = window.turnstile;\n if (!t || typeof t.render !== 'function') {\n throw new CaptchaShieldError('Global integrity check failed: window.turnstile.render is missing.');\n }\n}\n\nexport function requireDom() {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n throw new CaptchaShieldError('Library requires a browser DOM environment.');\n }\n}\n","import { CaptchaShieldError } from './errors';\n\n/**\n * Executes a fetch request with a timeout.\n * \n * FIX: This implementation now properly cleans up event listeners on AbortSignals to prevent memory leaks.\n */\nexport async function fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<Response> {\n const timeoutController = new AbortController();\n const merged = mergeSignals([init.signal, timeoutController.signal].filter(Boolean) as AbortSignal[]);\n \n const timer = setTimeout(() => timeoutController.abort(new CaptchaShieldError('Request timed out')), timeoutMs);\n \n try {\n const signal = merged ? merged.signal : init.signal;\n return await fetch(url, { ...init, signal });\n } finally {\n clearTimeout(timer);\n merged?.cleanup();\n }\n}\n\nexport function isExpectedStatus(status: number, expected: number | ((status: number) => boolean)): boolean {\n if (typeof expected === 'function') {\n return expected(status);\n }\n return status === expected;\n}\n\n/**\n * Merges multiple AbortSignals into one.\n * Returns a tuple of the new signal and a cleanup function.\n */\nfunction mergeSignals(signals: AbortSignal[]): { signal: AbortSignal; cleanup: () => void } | undefined {\n if (signals.length === 0) return undefined;\n\n const controller = new AbortController();\n const abortHandler = (event: Event) => {\n const signal = event.target as AbortSignal | null;\n controller.abort(signal?.reason);\n };\n\n signals.forEach(sig => {\n if (sig.aborted) {\n if (!controller.signal.aborted) controller.abort(sig.reason);\n } else {\n sig.addEventListener('abort', abortHandler, { once: true });\n }\n });\n\n const cleanup = () => {\n signals.forEach(sig => sig.removeEventListener('abort', abortHandler));\n };\n\n if (controller.signal.aborted) {\n cleanup();\n return { signal: controller.signal, cleanup: () => {} };\n }\n\n controller.signal.addEventListener('abort', cleanup, { once: true });\n\n return { signal: controller.signal, cleanup };\n}\n","import { fetchWithTimeout, isExpectedStatus } from './network';\nimport { ResolvedStatusOptions, ResolvedVerifyOptions } from './types';\nimport { CaptchaShieldError } from './errors';\n\nexport async function runStatusCheck(status: ResolvedStatusOptions) {\n if (!status.endpoint) return;\n const response = await fetchWithTimeout(status.endpoint, { method: 'GET' }, status.timeoutMs);\n if (!isExpectedStatus(response.status, status.expectedStatus)) {\n throw new CaptchaShieldError(`Status check failed with status: ${response.status}`);\n }\n}\n\nexport async function verifyTokenWithServer(token: string, verify: ResolvedVerifyOptions): Promise<boolean> {\n if (!verify.endpoint) return true;\n\n let lastError: Error | null = null;\n for (let attempt = 0; attempt <= verify.retries; attempt++) {\n try {\n const response = await requestVerification(token, verify);\n if (isExpectedStatus(response.status, verify.expectedStatus)) {\n return true;\n }\n lastError = new CaptchaShieldError(`Verification failed with status: ${response.status}`);\n } catch (err) {\n lastError = err instanceof Error ? err : new CaptchaShieldError(String(err));\n }\n }\n\n if (lastError) {\n throw lastError;\n }\n\n return false;\n}\n\nasync function requestVerification(token: string, verify: ResolvedVerifyOptions): Promise<Response> {\n const init: RequestInit = {\n method: verify.method,\n headers: verify.headers,\n };\n\n const body = verify.buildBody(token);\n if (body) {\n init.body = body;\n }\n\n return fetchWithTimeout(verify.endpoint ?? '', init, verify.timeoutMs);\n}\n","import { clearCookie, deriveCookieName, hasCookie, setCookie } from './cookies';\nimport { renderDefaultModal } from './renderer';\nimport { ensureTurnstile, requireDom } from './turnstile';\nimport { runStatusCheck, verifyTokenWithServer } from './verification';\nimport { CaptchaShieldError } from './errors';\nimport { validateRequestEndpoint, validateTurnstileScriptUrl } from './validation';\nimport {\n CookieOptions,\n IntegrityWatch,\n ModalCopyOptions,\n ModalOptions,\n ModalStyleOptions,\n ResolvedCookieOptions,\n ResolvedIntegrityOptions,\n ResolvedShieldConfig,\n ResolvedStatusOptions,\n ResolvedVerifyOptions,\n ShieldConfig,\n ShieldController,\n ShieldOpenResult,\n ShieldRenderer,\n TurnstileGlobal,\n} from './types';\n\nexport const DEFAULT_SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';\nexport const DEFAULT_COOKIE_NAME = 'captchaShieldVerified';\n\nconst DEFAULT_MODAL_COPY: Required<ModalCopyOptions> = {\n title: 'Please verify you are human',\n body: 'Complete the Cloudflare Turnstile check to continue. A short-lived cookie will skip this step until it expires.',\n helperText: 'No data is stored beyond the verification cookie and the Turnstile token.',\n};\n\nconst DEFAULT_MODAL_STYLES: Required<ModalStyleOptions> = {\n overlayClass: 'captcha-shield__overlay',\n panelClass: 'captcha-shield__panel',\n titleClass: 'captcha-shield__title',\n bodyClass: 'captcha-shield__body',\n helperClass: 'captcha-shield__helper',\n customCss: '',\n};\n\nconst DEFAULT_MODAL: Required<ModalOptions> & { copy: Required<ModalCopyOptions>; styles: Required<ModalStyleOptions> } = {\n copy: DEFAULT_MODAL_COPY,\n styles: DEFAULT_MODAL_STYLES,\n ariaLabel: 'Human verification dialog',\n closeOnVerify: true,\n injectDefaultStyle: true,\n};\n\nconst DEFAULT_COOKIE: ResolvedCookieOptions = {\n name: DEFAULT_COOKIE_NAME,\n maxAgeSeconds: 60 * 60 * 24,\n path: '/',\n sameSite: 'Lax',\n secure: true,\n scopeId: undefined,\n useScopePrefix: false,\n trustClientCookie: false,\n};\n\nconst DEFAULT_VERIFY: ResolvedVerifyOptions = {\n endpoint: undefined,\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n timeoutMs: 5000,\n retries: 1,\n buildBody: (token: string) => JSON.stringify({ token }),\n expectedStatus: (status: number) => status >= 200 && status < 300,\n};\n\nconst DEFAULT_STATUS: ResolvedStatusOptions = {\n endpoint: undefined,\n timeoutMs: 3000,\n expectedStatus: (status: number) => status >= 200 && status < 400,\n};\n\nconst DEFAULT_INTEGRITY: ResolvedIntegrityOptions = {\n scriptIntegrity: undefined,\n verifyTurnstileGlobal: true,\n enforceChallengePresence: true,\n monitorChallengeRemoval: false,\n};\n\nexport function createCaptchaShield(config: ShieldConfig): ShieldController {\n const resolved = resolveConfig(config);\n\n let verified = false;\n let token: string | null = null;\n let rendererHandle: { root: HTMLElement; destroy?: () => void } | null = null;\n let challengeTarget: HTMLElement | null = null;\n let widgetId: string | null = null;\n let opening: Promise<ShieldOpenResult> | null = null;\n let lastTurnstile: TurnstileGlobal | null = null;\n let verifying: Promise<void> | null = null;\n let integrityWatch: IntegrityWatch | null = null;\n let currentOpenId = 0; // Tracks the validity of the current open request\n\n const isAlreadyVerified = () =>\n verified || (resolved.cookie.trustClientCookie && hasCookie(resolved.cookie.name));\n\n const close = () => {\n // Invalidate any pending open operations\n currentOpenId++;\n\n if (widgetId && lastTurnstile?.remove) {\n lastTurnstile.remove(widgetId);\n widgetId = null;\n }\n\n if (rendererHandle) {\n rendererHandle.destroy?.();\n rendererHandle.root.remove();\n rendererHandle = null;\n }\n if (integrityWatch) {\n integrityWatch.stop();\n integrityWatch = null;\n }\n challengeTarget = null;\n };\n\n const reset = () => {\n token = null;\n verified = false;\n clearCookie(resolved.cookie);\n if (widgetId && lastTurnstile?.reset) {\n lastTurnstile.reset(widgetId);\n }\n widgetId = null;\n };\n\n const destroy = () => {\n reset();\n close();\n };\n\n const renderModal = (turnstile: TurnstileGlobal) => {\n const challengeContainer = document.createElement('div');\n challengeContainer.setAttribute('data-captcha-shield', 'challenge');\n\n const handle = resolved.render\n ? resolved.render({ challengeContainer, config: resolved, close })\n : renderDefaultModal({ challengeContainer, config: resolved, close });\n\n if (!handle.root.contains(challengeContainer)) {\n handle.root.appendChild(challengeContainer);\n }\n\n document.body.appendChild(handle.root);\n lastTurnstile = turnstile;\n rendererHandle = handle;\n challengeTarget = challengeContainer;\n\n if (resolved.integrity.monitorChallengeRemoval) {\n integrityWatch = startIntegrityWatch(challengeContainer, () => {\n handleError('Challenge container was removed. Possible tampering detected.');\n destroy();\n });\n }\n };\n\n const open = async (): Promise<ShieldOpenResult> => {\n const cookieVerified = resolved.cookie.trustClientCookie && hasCookie(resolved.cookie.name);\n if (verified || cookieVerified) {\n verified = true;\n return { status: 'already-verified', reason: cookieVerified ? 'cookie' : 'session' };\n }\n\n if (opening) {\n return opening;\n }\n\n // Associate this request with an ID to detect cancellation/closure during async steps\n const myId = ++currentOpenId;\n\n opening = (async () => {\n requireDom();\n await runStatusCheck(resolved.statusCheck);\n if (myId !== currentOpenId) throw new CaptchaShieldError('Operation cancelled by close()');\n\n const turnstile = await ensureTurnstile(resolved.turnstileScriptUrl, resolved.integrity);\n if (myId !== currentOpenId) throw new CaptchaShieldError('Operation cancelled by close()');\n\n renderModal(turnstile);\n if (!challengeTarget) {\n if (resolved.integrity.enforceChallengePresence) {\n throw new CaptchaShieldError('Failed to mount a challenge container.');\n }\n challengeTarget = document.createElement('div');\n }\n\n widgetId = turnstile.render(challengeTarget, {\n sitekey: resolved.siteKey,\n action: resolved.action,\n cData: resolved.cData,\n callback: handleVerified,\n 'error-callback': (message?: string) => handleError(message ?? 'Turnstile error'),\n 'timeout-callback': () => handleError('Turnstile timed out'),\n });\n\n return { status: 'rendered' as const };\n })();\n\n try {\n return await opening;\n } finally {\n opening = null;\n }\n };\n\n const handleVerified = (turnstileToken: string) => {\n token = turnstileToken;\n const finalize = () => {\n verified = true;\n if (resolved.cookie.trustClientCookie) {\n setCookie(resolved.cookie, '1');\n }\n resolved.onVerified?.(turnstileToken);\n if (resolved.modal.closeOnVerify) {\n close();\n }\n };\n\n const resetWidget = () => {\n if (widgetId && lastTurnstile?.reset) {\n lastTurnstile.reset(widgetId);\n }\n };\n\n const executeVerification = async () => {\n if (!resolved.verify.endpoint) {\n finalize();\n return;\n }\n\n const ok = await verifyTokenWithServer(turnstileToken, resolved.verify);\n if (!ok) {\n resetWidget();\n handleError('Server verification rejected token');\n return;\n }\n\n finalize();\n };\n\n if (!verifying) {\n verifying = executeVerification()\n .catch((err) => {\n resetWidget();\n handleError(normalizeErrorMessage(err));\n })\n .finally(() => {\n verifying = null;\n });\n }\n };\n\n const handleError = (message: string) => {\n resolved.onError?.(new CaptchaShieldError(message));\n };\n\n return {\n open,\n close,\n reset,\n destroy,\n isVerified: () => isAlreadyVerified(),\n getToken: () => token,\n };\n}\n\nfunction resolveConfig(config: ShieldConfig): ResolvedShieldConfig {\n if (!config.siteKey) {\n throw new CaptchaShieldError('Configuration missing required \"siteKey\".');\n }\n\n const modalCopy = { ...DEFAULT_MODAL_COPY, ...(config.modal?.copy ?? {}) };\n const modalStyles = { ...DEFAULT_MODAL_STYLES, ...(config.modal?.styles ?? {}) };\n const modal = {\n ...DEFAULT_MODAL,\n ...(config.modal ?? {}),\n copy: modalCopy,\n styles: modalStyles,\n };\n\n const cookieBase: CookieOptions = config.cookie ?? {};\n const cookie: ResolvedCookieOptions = { ...DEFAULT_COOKIE, ...cookieBase };\n cookie.name = deriveCookieName(cookieBase.name ?? DEFAULT_COOKIE_NAME, cookie);\n\n const verify = { ...DEFAULT_VERIFY, ...(config.verify ?? {}) };\n const statusCheck = { ...DEFAULT_STATUS, ...(config.statusCheck ?? {}) };\n const integrity = { ...DEFAULT_INTEGRITY, ...(config.integrity ?? {}) };\n\n if (config.verify?.method && config.verify.method !== 'POST') {\n throw new CaptchaShieldError('verify.method only supports \"POST\".');\n }\n\n const turnstileScriptUrl = validateTurnstileScriptUrl(config.turnstileScriptUrl ?? DEFAULT_SCRIPT_URL);\n if (verify.endpoint) {\n verify.endpoint = validateRequestEndpoint(verify.endpoint, 'verify.endpoint');\n }\n if (statusCheck.endpoint) {\n statusCheck.endpoint = validateRequestEndpoint(statusCheck.endpoint, 'statusCheck.endpoint');\n }\n\n return {\n siteKey: config.siteKey,\n action: config.action,\n cData: config.cData,\n turnstileScriptUrl,\n modal,\n cookie,\n verify,\n statusCheck,\n integrity,\n render: config.render as ShieldRenderer | undefined,\n onVerified: config.onVerified,\n onError: config.onError,\n };\n}\n\nfunction startIntegrityWatch(element: HTMLElement, onTamper: () => void): IntegrityWatch {\n if (!element.isConnected) {\n onTamper();\n return { stop: () => undefined };\n }\n\n const observer = new MutationObserver(() => {\n if (!element.isConnected) {\n onTamper();\n observer.disconnect();\n }\n });\n\n observer.observe(document.body, { childList: true, subtree: true });\n\n return {\n observer,\n stop: () => observer.disconnect(),\n };\n}\n\nfunction normalizeErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n return error.message.replace(/^\\[CaptchaShield\\]\\s*/, '');\n }\n\n return String(error);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,181 @@
1
+ declare global {
2
+ interface Window {
3
+ turnstile?: TurnstileGlobal;
4
+ }
5
+ }
6
+ interface TurnstileGlobal {
7
+ render: (element: HTMLElement, options: TurnstileRenderOptions) => string;
8
+ reset?: (widgetId?: string) => void;
9
+ remove?: (widgetId: string) => void;
10
+ }
11
+ interface TurnstileRenderOptions {
12
+ sitekey: string;
13
+ action?: string;
14
+ cData?: string;
15
+ callback?: (token: string) => void;
16
+ 'error-callback'?: (message?: string) => void;
17
+ 'timeout-callback'?: () => void;
18
+ }
19
+ interface CookieOptions {
20
+ name?: string;
21
+ maxAgeSeconds?: number;
22
+ path?: string;
23
+ domain?: string;
24
+ sameSite?: 'Strict' | 'Lax' | 'None';
25
+ secure?: boolean;
26
+ scopeId?: string;
27
+ useScopePrefix?: boolean;
28
+ /**
29
+ * When enabled, a client-set cookie is allowed to skip the challenge on later visits.
30
+ *
31
+ * Keep this disabled unless challenge bypass is purely UX and your backend enforces its
32
+ * own verification on every protected action.
33
+ */
34
+ trustClientCookie?: boolean;
35
+ }
36
+ interface ModalCopyOptions {
37
+ title?: string;
38
+ body?: string;
39
+ helperText?: string;
40
+ }
41
+ interface ModalStyleOptions {
42
+ overlayClass?: string;
43
+ panelClass?: string;
44
+ titleClass?: string;
45
+ bodyClass?: string;
46
+ helperClass?: string;
47
+ /**
48
+ * Custom CSS to inject into the document head.
49
+ *
50
+ * @security **Warning**: This string is injected directly into a <style> tag.
51
+ * Do NOT pass user-generated content here as it may lead to CSS injection attacks
52
+ * (e.g., data exfiltration via background images). Ensure this content is static or sanitized.
53
+ */
54
+ customCss?: string;
55
+ }
56
+ interface ModalOptions {
57
+ copy?: ModalCopyOptions;
58
+ styles?: ModalStyleOptions;
59
+ ariaLabel?: string;
60
+ closeOnVerify?: boolean;
61
+ injectDefaultStyle?: boolean;
62
+ }
63
+ interface VerifyOptions {
64
+ endpoint?: string;
65
+ method?: 'POST';
66
+ headers?: Record<string, string>;
67
+ timeoutMs?: number;
68
+ retries?: number;
69
+ buildBody?: (token: string) => BodyInit | null | undefined;
70
+ expectedStatus?: number | ((status: number) => boolean);
71
+ }
72
+ interface StatusOptions {
73
+ endpoint?: string;
74
+ timeoutMs?: number;
75
+ expectedStatus?: number | ((status: number) => boolean);
76
+ }
77
+ interface IntegrityOptions {
78
+ scriptIntegrity?: string;
79
+ verifyTurnstileGlobal?: boolean;
80
+ enforceChallengePresence?: boolean;
81
+ monitorChallengeRemoval?: boolean;
82
+ }
83
+ interface ShieldConfig {
84
+ siteKey: string;
85
+ action?: string;
86
+ cData?: string;
87
+ turnstileScriptUrl?: string;
88
+ modal?: ModalOptions;
89
+ cookie?: CookieOptions;
90
+ verify?: VerifyOptions;
91
+ statusCheck?: StatusOptions;
92
+ integrity?: IntegrityOptions;
93
+ render?: ShieldRenderer;
94
+ onVerified?: (token: string) => void;
95
+ onError?: (error: Error) => void;
96
+ }
97
+ interface ShieldOpenResult {
98
+ status: 'rendered' | 'already-verified';
99
+ reason?: 'cookie' | 'session';
100
+ }
101
+ interface ShieldController {
102
+ open: () => Promise<ShieldOpenResult>;
103
+ close: () => void;
104
+ reset: () => void;
105
+ destroy: () => void;
106
+ isVerified: () => boolean;
107
+ getToken: () => string | null;
108
+ }
109
+ interface RendererContext {
110
+ challengeContainer: HTMLElement;
111
+ config: ResolvedShieldConfig;
112
+ close: () => void;
113
+ }
114
+ interface RendererHandle {
115
+ root: HTMLElement;
116
+ destroy?: () => void;
117
+ }
118
+ type ShieldRenderer = (context: RendererContext) => RendererHandle;
119
+ interface ResolvedCookieOptions {
120
+ name: string;
121
+ maxAgeSeconds: number;
122
+ path: string;
123
+ domain?: string;
124
+ sameSite?: 'Strict' | 'Lax' | 'None';
125
+ secure: boolean;
126
+ scopeId?: string;
127
+ useScopePrefix: boolean;
128
+ trustClientCookie: boolean;
129
+ }
130
+ interface ResolvedVerifyOptions {
131
+ endpoint?: string;
132
+ method: 'POST';
133
+ headers: Record<string, string>;
134
+ timeoutMs: number;
135
+ retries: number;
136
+ buildBody: (token: string) => BodyInit | null | undefined;
137
+ expectedStatus: number | ((status: number) => boolean);
138
+ }
139
+ interface ResolvedStatusOptions {
140
+ endpoint?: string;
141
+ timeoutMs: number;
142
+ expectedStatus: number | ((status: number) => boolean);
143
+ }
144
+ interface ResolvedIntegrityOptions {
145
+ scriptIntegrity?: string;
146
+ verifyTurnstileGlobal: boolean;
147
+ enforceChallengePresence: boolean;
148
+ monitorChallengeRemoval: boolean;
149
+ }
150
+ interface ResolvedShieldConfig {
151
+ siteKey: string;
152
+ action?: string;
153
+ cData?: string;
154
+ turnstileScriptUrl: string;
155
+ modal: Required<ModalOptions> & {
156
+ copy: Required<ModalCopyOptions>;
157
+ styles: Required<ModalStyleOptions>;
158
+ };
159
+ cookie: ResolvedCookieOptions;
160
+ verify: ResolvedVerifyOptions;
161
+ statusCheck: ResolvedStatusOptions;
162
+ integrity: ResolvedIntegrityOptions;
163
+ render?: ShieldRenderer;
164
+ onVerified?: (token: string) => void;
165
+ onError?: (error: Error) => void;
166
+ }
167
+
168
+ declare const DEFAULT_SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
169
+ declare const DEFAULT_COOKIE_NAME = "captchaShieldVerified";
170
+ declare function createCaptchaShield(config: ShieldConfig): ShieldController;
171
+
172
+ declare function hasCookie(name: string): boolean;
173
+ declare function setCookie(options: ResolvedCookieOptions, value: string): void;
174
+ declare function clearCookie(options: ResolvedCookieOptions): void;
175
+
176
+ declare class CaptchaShieldError extends Error {
177
+ originalError?: unknown | undefined;
178
+ constructor(message: string, originalError?: unknown | undefined);
179
+ }
180
+
181
+ export { CaptchaShieldError, type CookieOptions, DEFAULT_COOKIE_NAME, DEFAULT_SCRIPT_URL, type IntegrityOptions, type ModalCopyOptions, type ModalOptions, type ModalStyleOptions, type RendererContext, type RendererHandle, type ResolvedShieldConfig, type ShieldConfig, type ShieldController, type ShieldOpenResult, type ShieldRenderer, type StatusOptions, type VerifyOptions, clearCookie, createCaptchaShield, hasCookie, setCookie };
@@ -0,0 +1,181 @@
1
+ declare global {
2
+ interface Window {
3
+ turnstile?: TurnstileGlobal;
4
+ }
5
+ }
6
+ interface TurnstileGlobal {
7
+ render: (element: HTMLElement, options: TurnstileRenderOptions) => string;
8
+ reset?: (widgetId?: string) => void;
9
+ remove?: (widgetId: string) => void;
10
+ }
11
+ interface TurnstileRenderOptions {
12
+ sitekey: string;
13
+ action?: string;
14
+ cData?: string;
15
+ callback?: (token: string) => void;
16
+ 'error-callback'?: (message?: string) => void;
17
+ 'timeout-callback'?: () => void;
18
+ }
19
+ interface CookieOptions {
20
+ name?: string;
21
+ maxAgeSeconds?: number;
22
+ path?: string;
23
+ domain?: string;
24
+ sameSite?: 'Strict' | 'Lax' | 'None';
25
+ secure?: boolean;
26
+ scopeId?: string;
27
+ useScopePrefix?: boolean;
28
+ /**
29
+ * When enabled, a client-set cookie is allowed to skip the challenge on later visits.
30
+ *
31
+ * Keep this disabled unless challenge bypass is purely UX and your backend enforces its
32
+ * own verification on every protected action.
33
+ */
34
+ trustClientCookie?: boolean;
35
+ }
36
+ interface ModalCopyOptions {
37
+ title?: string;
38
+ body?: string;
39
+ helperText?: string;
40
+ }
41
+ interface ModalStyleOptions {
42
+ overlayClass?: string;
43
+ panelClass?: string;
44
+ titleClass?: string;
45
+ bodyClass?: string;
46
+ helperClass?: string;
47
+ /**
48
+ * Custom CSS to inject into the document head.
49
+ *
50
+ * @security **Warning**: This string is injected directly into a <style> tag.
51
+ * Do NOT pass user-generated content here as it may lead to CSS injection attacks
52
+ * (e.g., data exfiltration via background images). Ensure this content is static or sanitized.
53
+ */
54
+ customCss?: string;
55
+ }
56
+ interface ModalOptions {
57
+ copy?: ModalCopyOptions;
58
+ styles?: ModalStyleOptions;
59
+ ariaLabel?: string;
60
+ closeOnVerify?: boolean;
61
+ injectDefaultStyle?: boolean;
62
+ }
63
+ interface VerifyOptions {
64
+ endpoint?: string;
65
+ method?: 'POST';
66
+ headers?: Record<string, string>;
67
+ timeoutMs?: number;
68
+ retries?: number;
69
+ buildBody?: (token: string) => BodyInit | null | undefined;
70
+ expectedStatus?: number | ((status: number) => boolean);
71
+ }
72
+ interface StatusOptions {
73
+ endpoint?: string;
74
+ timeoutMs?: number;
75
+ expectedStatus?: number | ((status: number) => boolean);
76
+ }
77
+ interface IntegrityOptions {
78
+ scriptIntegrity?: string;
79
+ verifyTurnstileGlobal?: boolean;
80
+ enforceChallengePresence?: boolean;
81
+ monitorChallengeRemoval?: boolean;
82
+ }
83
+ interface ShieldConfig {
84
+ siteKey: string;
85
+ action?: string;
86
+ cData?: string;
87
+ turnstileScriptUrl?: string;
88
+ modal?: ModalOptions;
89
+ cookie?: CookieOptions;
90
+ verify?: VerifyOptions;
91
+ statusCheck?: StatusOptions;
92
+ integrity?: IntegrityOptions;
93
+ render?: ShieldRenderer;
94
+ onVerified?: (token: string) => void;
95
+ onError?: (error: Error) => void;
96
+ }
97
+ interface ShieldOpenResult {
98
+ status: 'rendered' | 'already-verified';
99
+ reason?: 'cookie' | 'session';
100
+ }
101
+ interface ShieldController {
102
+ open: () => Promise<ShieldOpenResult>;
103
+ close: () => void;
104
+ reset: () => void;
105
+ destroy: () => void;
106
+ isVerified: () => boolean;
107
+ getToken: () => string | null;
108
+ }
109
+ interface RendererContext {
110
+ challengeContainer: HTMLElement;
111
+ config: ResolvedShieldConfig;
112
+ close: () => void;
113
+ }
114
+ interface RendererHandle {
115
+ root: HTMLElement;
116
+ destroy?: () => void;
117
+ }
118
+ type ShieldRenderer = (context: RendererContext) => RendererHandle;
119
+ interface ResolvedCookieOptions {
120
+ name: string;
121
+ maxAgeSeconds: number;
122
+ path: string;
123
+ domain?: string;
124
+ sameSite?: 'Strict' | 'Lax' | 'None';
125
+ secure: boolean;
126
+ scopeId?: string;
127
+ useScopePrefix: boolean;
128
+ trustClientCookie: boolean;
129
+ }
130
+ interface ResolvedVerifyOptions {
131
+ endpoint?: string;
132
+ method: 'POST';
133
+ headers: Record<string, string>;
134
+ timeoutMs: number;
135
+ retries: number;
136
+ buildBody: (token: string) => BodyInit | null | undefined;
137
+ expectedStatus: number | ((status: number) => boolean);
138
+ }
139
+ interface ResolvedStatusOptions {
140
+ endpoint?: string;
141
+ timeoutMs: number;
142
+ expectedStatus: number | ((status: number) => boolean);
143
+ }
144
+ interface ResolvedIntegrityOptions {
145
+ scriptIntegrity?: string;
146
+ verifyTurnstileGlobal: boolean;
147
+ enforceChallengePresence: boolean;
148
+ monitorChallengeRemoval: boolean;
149
+ }
150
+ interface ResolvedShieldConfig {
151
+ siteKey: string;
152
+ action?: string;
153
+ cData?: string;
154
+ turnstileScriptUrl: string;
155
+ modal: Required<ModalOptions> & {
156
+ copy: Required<ModalCopyOptions>;
157
+ styles: Required<ModalStyleOptions>;
158
+ };
159
+ cookie: ResolvedCookieOptions;
160
+ verify: ResolvedVerifyOptions;
161
+ statusCheck: ResolvedStatusOptions;
162
+ integrity: ResolvedIntegrityOptions;
163
+ render?: ShieldRenderer;
164
+ onVerified?: (token: string) => void;
165
+ onError?: (error: Error) => void;
166
+ }
167
+
168
+ declare const DEFAULT_SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
169
+ declare const DEFAULT_COOKIE_NAME = "captchaShieldVerified";
170
+ declare function createCaptchaShield(config: ShieldConfig): ShieldController;
171
+
172
+ declare function hasCookie(name: string): boolean;
173
+ declare function setCookie(options: ResolvedCookieOptions, value: string): void;
174
+ declare function clearCookie(options: ResolvedCookieOptions): void;
175
+
176
+ declare class CaptchaShieldError extends Error {
177
+ originalError?: unknown | undefined;
178
+ constructor(message: string, originalError?: unknown | undefined);
179
+ }
180
+
181
+ export { CaptchaShieldError, type CookieOptions, DEFAULT_COOKIE_NAME, DEFAULT_SCRIPT_URL, type IntegrityOptions, type ModalCopyOptions, type ModalOptions, type ModalStyleOptions, type RendererContext, type RendererHandle, type ResolvedShieldConfig, type ShieldConfig, type ShieldController, type ShieldOpenResult, type ShieldRenderer, type StatusOptions, type VerifyOptions, clearCookie, createCaptchaShield, hasCookie, setCookie };