├── .husky ├── .gitignore └── pre-commit ├── .npmrc ├── .mise.toml ├── packages ├── docs │ ├── content │ │ ├── 1.guide │ │ │ ├── _dir.yml │ │ │ ├── 4.faq.md │ │ │ ├── 3.enterprise.md │ │ │ ├── 2.migration.md │ │ │ ├── 1.nuxt.md │ │ │ └── 0.index.md │ │ ├── 2.components │ │ │ ├── _dir.yml │ │ │ ├── 0.index.md │ │ │ ├── 1.checkbox.md │ │ │ ├── 3.challenge-v3.md │ │ │ └── 2.challenge-v2.md │ │ ├── 3.composables │ │ │ ├── _dir.yml │ │ │ ├── 0.recaptcha-provider.md │ │ │ ├── 2.challenge-v3.md │ │ │ └── 1.challenge-v2.md │ │ ├── 4.advanced │ │ │ ├── _dir.yml │ │ │ └── 0.customize-recaptcha-script-loading.md │ │ ├── 5.integration │ │ │ ├── _dir.yml │ │ │ └── 0.vee-validation.md │ │ └── 0.index.md │ ├── .npmrc │ ├── tsconfig.json │ ├── public │ │ ├── icon.png │ │ ├── favicon.ico │ │ ├── preview.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── site.webmanifest │ │ ├── logo-light.svg │ │ └── logo-dark.svg │ ├── renovate.json │ ├── vite.config.mts │ ├── .gitignore │ ├── components │ │ └── content │ │ │ ├── PrimaryButton.vue │ │ │ ├── ChallengeV3Demo.vue │ │ │ ├── CheckboxDemo.vue │ │ │ ├── IndexDemo.vue │ │ │ ├── Mermaid.vue │ │ │ ├── ChallengeV2Demo.vue │ │ │ ├── ThemeButton.vue │ │ │ ├── VeeValidateInvisible.vue │ │ │ └── VeeValidateCheckbox.vue │ ├── .env.example │ ├── app.vue │ ├── layouts │ │ └── default.vue │ ├── tokens.config.ts │ ├── app.config.ts │ ├── tests │ │ └── e2e.spec.ts │ ├── package.json │ ├── nuxt.config.ts │ ├── README.md │ └── moon.yml ├── playground-vite │ ├── README.md │ ├── moon.yml │ ├── vite.config.ts │ ├── package.json │ ├── index.html │ ├── src │ │ ├── main.ts │ │ └── App.vue │ └── tsconfig.json ├── playground-nuxt │ ├── server │ │ └── tsconfig.json │ ├── public │ │ └── favicon.ico │ ├── tsconfig.json │ ├── moon.yml │ ├── .gitignore │ ├── nuxt.config.ts │ ├── package.json │ ├── README.md │ └── app.vue └── vue-recaptcha │ ├── src │ ├── unhead.ts │ ├── index.ts │ ├── types.ts │ ├── script-manager │ │ ├── __tests__ │ │ │ ├── common.test-d.ts │ │ │ └── head.spec.ts │ │ ├── head.ts │ │ ├── unhead.ts │ │ └── common.ts │ ├── nuxt-plugin.ts │ ├── composables │ │ ├── __tests__ │ │ │ └── context.spec.ts │ │ ├── script-provider.ts │ │ ├── challenge-v3.ts │ │ ├── proxy.ts │ │ ├── component-v2.ts │ │ ├── context.ts │ │ └── challenge-v2.ts │ ├── utils.ts │ ├── nuxt-enterprise-plugin.ts │ ├── api.ts │ ├── components │ │ ├── ChallengeV3.vue │ │ ├── Checkbox.vue │ │ └── ChallengeV2.vue │ ├── plugin.ts │ └── nuxt.ts │ ├── .npmignore │ ├── index.html │ ├── example │ ├── main.ts │ └── App.vue │ ├── vite.config.mts │ ├── tsdown.config.ts │ ├── CHANGELOG.md │ ├── moon.yml │ ├── playwright │ └── vue-recaptcha.spec.ts │ ├── playwright.config.ts │ ├── package.json │ └── tsconfig.json ├── .prettierrc.yml ├── .release-please-manifest.json ├── .lintstagedrc.yml ├── pnpm-workspace.yaml ├── .moon ├── toolchain.yml └── workspace.yml ├── renovate.json ├── .github ├── workflows │ ├── release-please.yml │ ├── doc.yml │ └── playwright.yml └── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug.yml ├── release-please-config.json ├── package.json ├── eslint.config.mjs ├── Readme.md ├── LICENSE ├── .vscode └── settings.json └── .gitignore /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | node = "22" 3 | -------------------------------------------------------------------------------- /packages/docs/content/1.guide/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Guide 2 | -------------------------------------------------------------------------------- /packages/playground-vite/README.md: -------------------------------------------------------------------------------- 1 | # playground-vite 2 | -------------------------------------------------------------------------------- /packages/docs/content/2.components/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Components 2 | -------------------------------------------------------------------------------- /packages/docs/content/3.composables/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Composables 2 | -------------------------------------------------------------------------------- /packages/docs/content/4.advanced/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Advanced Topics 2 | -------------------------------------------------------------------------------- /packages/docs/content/5.integration/_dir.yml: -------------------------------------------------------------------------------- 1 | title: Integration 2 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | printWidth: 120 3 | semi: false 4 | singleQuote: true 5 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages/vue-recaptcha": "3.0.0" 3 | } 4 | -------------------------------------------------------------------------------- /packages/docs/.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /packages/docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.lintstagedrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | '{e2e,example,src}/**/*.js': 3 | - prettier --write 4 | - eslint --fix 5 | -------------------------------------------------------------------------------- /packages/playground-nuxt/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | yarn test:unit 6 | -------------------------------------------------------------------------------- /packages/docs/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/icon.png -------------------------------------------------------------------------------- /packages/docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/favicon.ico -------------------------------------------------------------------------------- /packages/docs/public/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/preview.png -------------------------------------------------------------------------------- /packages/docs/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/favicon-16x16.png -------------------------------------------------------------------------------- /packages/docs/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/favicon-32x32.png -------------------------------------------------------------------------------- /packages/docs/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@nuxtjs"], 3 | "lockFileMaintenance": { 4 | "enabled": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/docs/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/playground-nuxt/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/playground-nuxt/public/favicon.ico -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/unhead.ts: -------------------------------------------------------------------------------- 1 | import { unheadScriptLoader } from './script-manager/unhead' 2 | 3 | export { unheadScriptLoader } 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | 4 | onlyBuiltDependencies: 5 | - '@parcel/watcher' 6 | - esbuild 7 | - vue-demi 8 | -------------------------------------------------------------------------------- /packages/playground-nuxt/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /packages/docs/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /packages/docs/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanSnow/vue-recaptcha/HEAD/packages/docs/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /packages/docs/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | import {} from 'vitest/config' 4 | 5 | export default defineConfig({}) 6 | -------------------------------------------------------------------------------- /.moon/toolchain.yml: -------------------------------------------------------------------------------- 1 | $schema: 'https://moonrepo.dev/schemas/toolchain.json' 2 | 3 | node: 4 | packageManager: pnpm 5 | syncPackageManagerField: false 6 | -------------------------------------------------------------------------------- /packages/docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | .output 13 | -------------------------------------------------------------------------------- /packages/docs/components/content/PrimaryButton.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /packages/playground-vite/moon.yml: -------------------------------------------------------------------------------- 1 | dependsOn: 2 | - vue-recaptcha 3 | 4 | tasks: 5 | dev: 6 | command: vite 7 | deps: 8 | - ^:build 9 | local: true 10 | -------------------------------------------------------------------------------- /packages/docs/.env.example: -------------------------------------------------------------------------------- 1 | # Create one with no scope selected on https://github.com/settings/tokens/new 2 | # This token is used for fetching the repository releases. 3 | GITHUB_TOKEN= -------------------------------------------------------------------------------- /packages/playground-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import Vue from '@vitejs/plugin-vue' 2 | import { defineConfig } from 'vite' 3 | 4 | export default defineConfig({ 5 | plugins: [Vue()], 6 | }) 7 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createPlugin } from './plugin' 2 | 3 | export * from './api' 4 | 5 | // plugin 6 | const plugin = createPlugin() 7 | export { plugin as default, plugin as VueRecaptchaPlugin } 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:js-lib"], 3 | "packageRules": [ 4 | { 5 | "matchUpdateTypes": ["patch", "minor", "pin"], 6 | "automerge": true 7 | } 8 | ], 9 | "postUpdateOptions": ["yarnDedupeHighest"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { GRecaptcha, NormalizedScriptLoaderFactory } from './script-manager/common' 2 | 3 | export interface RecaptchaPlugin { 4 | scriptLoader?: NormalizedScriptLoaderFactory 5 | getRecaptcha?: () => GRecaptcha 6 | } 7 | -------------------------------------------------------------------------------- /packages/playground-nuxt/moon.yml: -------------------------------------------------------------------------------- 1 | dependsOn: 2 | - vue-recaptcha 3 | 4 | tasks: 5 | prepare: 6 | command: 7 | - nuxt 8 | - prepare 9 | deps: 10 | - ^:build 11 | dev: 12 | command: 13 | - nuxt 14 | - dev 15 | deps: 16 | - ^:build 17 | local: true 18 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/script-manager/__tests__/common.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectTypeOf, it } from 'vitest' 2 | import { normalizeScriptLoaderOptions } from '../common' 3 | 4 | it('scriptLoaderOptionsInput', () => { 5 | expectTypeOf(normalizeScriptLoaderOptions).toBeCallableWith({ params: { render: 'explicit' } }) 6 | }) 7 | -------------------------------------------------------------------------------- /packages/playground-nuxt/.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .data 4 | .nuxt 5 | .nitro 6 | .cache 7 | dist 8 | 9 | # Node dependencies 10 | node_modules 11 | 12 | # Logs 13 | logs 14 | *.log 15 | 16 | # Misc 17 | .DS_Store 18 | .fleet 19 | .idea 20 | 21 | # Local env files 22 | .env 23 | .env.* 24 | !.env.example 25 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - v3 5 | name: release-please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: googleapis/release-please-action@v4 11 | with: 12 | release-type: node 13 | path: packages/vue-recaptcha 14 | -------------------------------------------------------------------------------- /packages/docs/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /packages/playground-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-recaptcha/playground-vite", 3 | "type": "module", 4 | "private": true, 5 | "dependencies": { 6 | "vue": "^3.5.14", 7 | "vue-recaptcha": "workspace:^" 8 | }, 9 | "devDependencies": { 10 | "@vitejs/plugin-vue": "6.0.3", 11 | "vite": "7.3.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a feature or enhancement 3 | labels: [enhancement] 4 | body: 5 | - type: textarea 6 | id: feature-description 7 | attributes: 8 | label: Describe the feature 9 | placeholder: Feature description 10 | validations: 11 | required: true 12 | -------------------------------------------------------------------------------- /packages/docs/content/3.composables/0.recaptcha-provider.md: -------------------------------------------------------------------------------- 1 | # useRecaptchaProvider 2 | 3 | The function that setup the required scripts for reCAPTCHA. It must be called in your app, or vue-recaptcha won't work. 4 | 5 | ```vue [Code] 6 | 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | e2e 3 | .babelrc 4 | .*ignore 5 | .eslintrc 6 | .prettierrc.yml 7 | .yarnclean 8 | .circleci 9 | .github 10 | .vscode 11 | rollup.config.js 12 | babel.config.js 13 | yarn.lock 14 | src 15 | __tests__ 16 | tests 17 | reports 18 | .yarn 19 | .lintstagedrc.yml 20 | renovate.json 21 | junit.xml 22 | jest.config.js 23 | .husky 24 | vue2-test 25 | -------------------------------------------------------------------------------- /packages/docs/components/content/ChallengeV3Demo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /packages/docs/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | "packages/vue-recaptcha": { 4 | "changelog-path": "CHANGELOG.md", 5 | "bump-minor-pre-major": false, 6 | "bump-patch-for-minor-pre-major": false, 7 | "draft": false, 8 | "prerelease": false 9 | } 10 | }, 11 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" 12 | } 13 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue Recaptcha Demo 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/docs/tokens.config.ts: -------------------------------------------------------------------------------- 1 | import { defineTheme } from 'pinceau' 2 | 3 | export default defineTheme({ 4 | color: { 5 | primary: { 6 | 50: '#FFF6E5', 7 | 100: '#FFEDCC', 8 | 200: '#FFDB99', 9 | 300: '#FFC966', 10 | 400: '#FFB833', 11 | 500: '#FFA500', 12 | 600: '#CC8500', 13 | 700: '#996300', 14 | 800: '#664200', 15 | 900: '#332100', 16 | }, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /packages/docs/components/content/CheckboxDemo.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | -------------------------------------------------------------------------------- /packages/playground-nuxt/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | modules: ['vue-recaptcha/nuxt'], 4 | devtools: { enabled: true }, 5 | runtimeConfig: { 6 | public: { 7 | recaptcha: { 8 | v2SiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', 9 | v3SiteKey: '6LejC9kZAAAAAFQyq2IjCq0eK4g88GkixXr4_BGs', 10 | }, 11 | }, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/playground-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue Recaptcha Playground 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/playground-vite/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createHead } from '@unhead/vue' 2 | import { createApp } from 'vue' 3 | import { VueRecaptchaPlugin } from 'vue-recaptcha' 4 | import App from './App.vue' 5 | 6 | const app = createApp(App) 7 | 8 | app.use(createHead()) 9 | app.use(VueRecaptchaPlugin, { 10 | v2SiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', 11 | v3SiteKey: '6LejC9kZAAAAAFQyq2IjCq0eK4g88GkixXr4_BGs', 12 | }) 13 | 14 | app.mount('#root') 15 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/example/main.ts: -------------------------------------------------------------------------------- 1 | import { createHead } from '@unhead/vue/client' 2 | import { createApp } from 'vue' 3 | import { VueRecaptchaPlugin } from '../src' 4 | import App from './App.vue' 5 | 6 | const app = createApp(App) 7 | 8 | app.use(createHead()) 9 | app.use(VueRecaptchaPlugin, { 10 | v2SiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', 11 | v3SiteKey: '6LejC9kZAAAAAFQyq2IjCq0eK4g88GkixXr4_BGs', 12 | }) 13 | 14 | app.mount('#root') 15 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/vite.config.mts: -------------------------------------------------------------------------------- 1 | import Vue from '@vitejs/plugin-vue' 2 | 3 | import { defineConfig } from 'vite' 4 | import {} from 'vitest/config' 5 | 6 | export default defineConfig({ 7 | plugins: [Vue()], 8 | test: { 9 | exclude: ['playwright/**', 'docs/**', '**/node_modules/**', 'dist/**'], 10 | environment: 'happy-dom', 11 | typecheck: { 12 | checker: 'vue-tsc', 13 | ignoreSourceErrors: true, 14 | }, 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/docs/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /packages/docs/components/content/IndexDemo.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /packages/docs/app.config.ts: -------------------------------------------------------------------------------- 1 | export default defineAppConfig({ 2 | docus: { 3 | title: 'vue-recaptcha', 4 | description: 'Document for vue-recaptcha', 5 | url: 'https://dansnow.github.io/vue-recaptcha', 6 | // image: '/social-card-preview.png', 7 | socials: { 8 | github: 'DanSnow/vue-recaptcha', 9 | }, 10 | github: { 11 | dir: 'docs/content', 12 | owner: 'DanSnow', 13 | repo: 'vue-recaptcha', 14 | branch: 'v3', 15 | edit: true, 16 | }, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "packageManager": "pnpm@10.26.0+sha512.3b3f6c725ebe712506c0ab1ad4133cf86b1f4b687effce62a9b38b4d72e3954242e643190fc51fa1642949c735f403debd44f5cb0edd657abe63a8b6a7e1e402", 4 | "scripts": { 5 | "lint": "eslint --fix ." 6 | }, 7 | "devDependencies": { 8 | "@antfu/eslint-config": "6.7.1", 9 | "@moonrepo/cli": "1.41.7", 10 | "eslint": "9.39.2", 11 | "eslint-plugin-prettier": "5.5.4", 12 | "playwright": "1.57.0", 13 | "typed-query-selector": "2.12.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/docs/components/content/Mermaid.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /packages/playground-nuxt/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-recaptcha/playground-nuxt", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "build": "nuxt build", 7 | "dev": "nuxt dev", 8 | "generate": "nuxt generate", 9 | "preview": "nuxt preview", 10 | "postinstall": "moon run --no-actions prepare" 11 | }, 12 | "dependencies": { 13 | "vue-recaptcha": "workspace:^" 14 | }, 15 | "devDependencies": { 16 | "nuxt": "3.20.2", 17 | "vue": "3.5.26", 18 | "vue-router": "4.6.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsdown' 2 | import Vue from 'unplugin-vue/rolldown' 3 | 4 | export default defineConfig({ 5 | entry: [ 6 | './src/index.ts', 7 | './src/unhead.ts', 8 | './src/nuxt.ts', 9 | './src/nuxt-plugin.ts', 10 | './src/nuxt-enterprise-plugin.ts', 11 | ], 12 | dts: { vue: true }, 13 | sourcemap: true, 14 | platform: 'neutral', 15 | exports: true, 16 | // Nuxt auto import 17 | external: ['#imports'], 18 | plugins: [Vue({ isProduction: true })], 19 | }) 20 | -------------------------------------------------------------------------------- /packages/docs/tests/e2e.spec.ts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'node:path' 2 | import { fileURLToPath } from 'node:url' 3 | import { expect, it } from 'vitest' 4 | import { fs } from 'zx' 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)) 7 | 8 | it('should generate queries', async () => { 9 | const queryPath = resolve(__dirname, '../dist/api/_content/query') 10 | const stat = await fs.stat(queryPath) 11 | expect(stat.isDirectory()).toBe(true) 12 | const files = await fs.readdir(queryPath) 13 | expect(files.length).toBeGreaterThan(0) 14 | }) 15 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/nuxt-plugin.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | // @ts-expect-error no types 3 | import { defineNuxtPlugin, useRuntimeConfig } from '#imports' 4 | import { createPlugin } from './plugin' 5 | 6 | import { unheadScriptLoader } from './script-manager/unhead' 7 | 8 | export default defineNuxtPlugin(({ vueApp }: { vueApp: App }) => { 9 | const { 10 | public: { recaptcha }, 11 | } = useRuntimeConfig() 12 | vueApp.use( 13 | createPlugin([ 14 | { 15 | scriptLoader: unheadScriptLoader, 16 | }, 17 | ]), 18 | recaptcha, 19 | ) 20 | }) 21 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.3](https://github.com/DanSnow/vue-recaptcha/compare/v2.0.2...v2.0.3) (2022-09-17) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * upgrade vue-demi to v0.13.11 to fix vue 2.7 warning ([d86f072](https://github.com/DanSnow/vue-recaptcha/commit/d86f072e1bb620f23e03aeaf1732b80074fdc7ed)) 9 | 10 | ## [2.0.2](https://github.com/DanSnow/vue-recaptcha/compare/v2.0.1...v2.0.2) (2022-09-17) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * upgrade vue-demi to v0.13.11 to fix vue 2.7 warning ([d86f072](https://github.com/DanSnow/vue-recaptcha/commit/d86f072e1bb620f23e03aeaf1732b80074fdc7ed)) 16 | -------------------------------------------------------------------------------- /packages/docs/components/content/ChallengeV2Demo.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /packages/docs/components/content/ThemeButton.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/composables/__tests__/context.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, it } from 'vitest' 2 | import { normalizeOptions } from '../context' 3 | 4 | it('normalizeOptions', () => { 5 | expect(normalizeOptions({ v2SiteKey: 'foo' })).toEqual({ 6 | v2SiteKey: 'foo', 7 | loaderOptions: { 8 | params: { 9 | render: 'explicit', 10 | }, 11 | }, 12 | }) 13 | 14 | expect(normalizeOptions({ v3SiteKey: 'bar' })).toEqual({ 15 | v3SiteKey: 'bar', 16 | loaderOptions: { 17 | params: { 18 | render: 'bar', 19 | }, 20 | }, 21 | }) 22 | 23 | expect(() => normalizeOptions({})).toThrow() 24 | }) 25 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/moon.yml: -------------------------------------------------------------------------------- 1 | $schema: "https://moonrepo.dev/schemas/project.json" 2 | 3 | platform: node 4 | 5 | tasks: 6 | build: 7 | command: tsdown 8 | inputs: 9 | - src/**/* 10 | outputs: 11 | - dist 12 | dev: 13 | command: vite 14 | lint: 15 | command: 16 | - eslint 17 | - . 18 | lint-fix: 19 | command: 20 | - eslint 21 | - --fix 22 | - . 23 | local: true 24 | test: 25 | command: noop 26 | deps: 27 | - ~:test-unit 28 | - ~:test-e2e 29 | platform: node 30 | test-e2e: 31 | command: 32 | - playwright 33 | - test 34 | test-unit: 35 | command: vitest 36 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { warn as vueWarn } from 'vue-demi' 2 | 3 | export function warn(msg: string, ...params: unknown[]) { 4 | vueWarn(`[vue-recaptcha]: ${msg}`, ...params) 5 | } 6 | 7 | export function invariant(condition: unknown, msg: string): asserts condition { 8 | if (!condition) { 9 | warn(msg) 10 | throw new Error(`Invariant violation: ${msg}`) 11 | } 12 | } 13 | 14 | export function getRecaptcha() { 15 | return window.grecaptcha 16 | } 17 | 18 | export function getEnterpriseRecaptcha() { 19 | invariant(window.grecaptcha.enterprise, 'Please load enterprise recaptcha script first') 20 | return window.grecaptcha.enterprise 21 | } 22 | -------------------------------------------------------------------------------- /packages/docs/content/1.guide/4.faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## What is "ReCAPTCHA couldn't find user-provided function: `__vueRecaptchaLoaded`"? 4 | It's because google's recaptcha have been loaded before your app. You can simply ignore it because vue-recaptcha can still detect and render recaptcha. If you care about this, try to move the script tag of recatpcha after to your app. 5 | 6 | ## How to test vue-recaptcha? 7 | You can mock `window.grecaptcha` to bypass google's recaptcha. 8 | 9 | ## How about an e2e testing (or integration testing)? 10 | Please reference to [recaptcha's FAQ](https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do). 11 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/composables/script-provider.ts: -------------------------------------------------------------------------------- 1 | import { onMounted } from 'vue-demi' 2 | import { checkRecaptchaLoad } from '../script-manager/common' 3 | import { warn } from '../utils' 4 | import { useRecaptchaContext } from './context' 5 | 6 | export interface UseRecaptchaProviderInput { 7 | _warnMessage?: string 8 | } 9 | 10 | export function useRecaptchaProvider() { 11 | const ctx = useRecaptchaContext() 12 | 13 | if (ctx.scriptInjected) { 14 | warn('`useRecaptchaProvider` is used multiple time') 15 | } else { 16 | ctx.scriptInjected = true 17 | ctx.useScriptProvider() 18 | 19 | onMounted(() => { 20 | checkRecaptchaLoad() 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/nuxt-enterprise-plugin.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | // @ts-expect-error no types 3 | import { defineNuxtPlugin, useRuntimeConfig } from '#imports' 4 | import { createPlugin } from './plugin' 5 | import { unheadScriptLoader } from './script-manager/unhead' 6 | 7 | import { getEnterpriseRecaptcha } from './utils' 8 | 9 | export default defineNuxtPlugin(({ vueApp }: { vueApp: App }) => { 10 | const { 11 | public: { recaptcha }, 12 | } = useRuntimeConfig() 13 | vueApp.use( 14 | createPlugin([ 15 | { 16 | scriptLoader: unheadScriptLoader, 17 | getRecaptcha: getEnterpriseRecaptcha, 18 | }, 19 | ]), 20 | recaptcha, 21 | ) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/script-manager/head.ts: -------------------------------------------------------------------------------- 1 | import { onMounted } from 'vue' 2 | import { defineScriptLoader, toQueryString } from './common' 3 | 4 | export const createHeadRecaptcha = defineScriptLoader((options) => { 5 | return () => { 6 | onMounted(() => { 7 | if (document.getElementById('vue-recaptcha')) { 8 | return 9 | } 10 | 11 | const script = document.createElement('script') 12 | script.src = `${options.recaptchaApiURL}?${toQueryString(options.params)}` 13 | script.async = true 14 | script.defer = true 15 | script.id = 'vue-recaptcha' 16 | if (options.nonce) script.nonce = options.nonce 17 | 18 | document.head.append(script) 19 | }) 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /packages/docs/content/2.components/0.index.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## [Checkbox](./components/checkbox) :badge[v2] 4 | The most basic form of the reCAPTCHA 5 | 6 | ::code-group 7 | ::code-block{label="Preview" preview} 8 | ::recaptcha-checkbox 9 | :: 10 | 11 | ```vue [Code] 12 | 15 | ``` 16 | :: 17 | 18 | :button-link[Go to document]{href="./components/checkbox"} 19 | 20 | ## [ChallengeV2](./components/challenge-v2) :badge[v2] 21 | Invisible reCAPTCHA. 22 | 23 | :button-link[Go to document]{href="./components/challenge-v2"} 24 | 25 | ## [ChallengeV3](./components/challenge-v3) :badge[v3] 26 | reCAPTCHA v3 27 | 28 | :button-link[Go to document]{href="./components/challenge-v3"} 29 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vue-recaptcha/doc", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "dev": "moon run dev --", 7 | "publish": "yarn generate && touch .output/public/.nojekyll && gh-pages -d dist -t" 8 | }, 9 | "dependencies": { 10 | "@nuxt/ui-templates": "^1.3.4", 11 | "@vueuse/nuxt": "^14.0.0", 12 | "mermaid": "^11.0.0", 13 | "ofetch": "^1.4.1", 14 | "vee-validate": "^4.15.0", 15 | "vue-recaptcha": "workspace:^", 16 | "yup": "^1.6.1" 17 | }, 18 | "devDependencies": { 19 | "@nuxt-themes/docus": "1.15.1", 20 | "@nuxtjs/mdc": "0.19.1", 21 | "@unocss/nuxt": "66.5.10", 22 | "gh-pages": "6.3.0", 23 | "nuxt": "3.20.2", 24 | "vitest": "3.2.4", 25 | "zx": "8.8.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.moon/workspace.yml: -------------------------------------------------------------------------------- 1 | # https://moonrepo.dev/docs/config/workspace 2 | $schema: 'https://moonrepo.dev/schemas/workspace.json' 3 | 4 | # Require a specific version of moon while running commands, otherwise fail. 5 | # versionConstraint: '>=1.0.0' 6 | 7 | # Extend and inherit an external configuration file. Must be a valid HTTPS URL or file system path. 8 | # extends: './shared/workspace.yml' 9 | 10 | # REQUIRED: A map of all projects found within the workspace, or a list or file system globs. 11 | # When using a map, each entry requires a unique project ID as the map key, and a file system 12 | # path to the project folder as the map value. File paths are relative from the workspace root, 13 | # and cannot reference projects located outside the workspace boundary. 14 | projects: 15 | - 'packages/*' 16 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | import eslintPluginPrettier from 'eslint-plugin-prettier' 3 | 4 | const ignores = ['node_modules/**', 'dist/**', 'lib/**', 'coverage/**', '.yarn/**', '**/*.md', '**/*.toml'] 5 | 6 | export default antfu( 7 | { 8 | ignores, 9 | stylistic: false, 10 | plugins: { 11 | prettier: eslintPluginPrettier, 12 | }, 13 | rules: { 14 | 'vue/html-self-closing': 'off', 15 | 'vue/singleline-html-element-content-newline': 'off', 16 | 17 | 'antfu/top-level-function': 'error', 18 | 'prettier/prettier': 'error', 19 | }, 20 | }, 21 | { 22 | files: ['packages/docs/**/*.ts', 'packages/docs/**/*.vue'], 23 | rules: { 24 | 'no-console': 'off', 25 | }, 26 | }, 27 | { ignores }, 28 | ) 29 | -------------------------------------------------------------------------------- /packages/docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | 3 | export default defineNuxtConfig({ 4 | extends: '@nuxt-themes/docus', 5 | app: { 6 | baseURL: '/vue-recaptcha/', 7 | }, 8 | modules: ['@unocss/nuxt', '@vueuse/nuxt', ['vue-recaptcha/nuxt', { _globalComponent: true }]], 9 | unocss: { 10 | // presets 11 | uno: true, // enabled `@unocss/preset-uno` 12 | icons: true, // enabled `@unocss/preset-icons` 13 | attributify: true, // enabled `@unocss/preset-attributify`, 14 | 15 | // core options 16 | shortcuts: [], 17 | rules: [], 18 | }, 19 | runtimeConfig: { 20 | public: { 21 | recaptcha: { 22 | v2SiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', 23 | v3SiteKey: '6LejC9kZAAAAAFQyq2IjCq0eK4g88GkixXr4_BGs', 24 | }, 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/composables/challenge-v3.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue-demi' 2 | import { ref } from 'vue-demi' 3 | import { useAssertV3SiteKey, useRecaptchaProxy } from './context' 4 | 5 | export interface UseChallengeV3Return { 6 | /** 7 | * reCAPTCHA v3 response as ref 8 | */ 9 | response: Ref 10 | /** 11 | * Execute the challenge 12 | * @returns response for reCAPTCHA v3 13 | */ 14 | execute: () => Promise 15 | } 16 | 17 | export function useChallengeV3(action: string): UseChallengeV3Return { 18 | const siteKey = useAssertV3SiteKey() 19 | const proxy = useRecaptchaProxy() 20 | const response = ref() 21 | 22 | return { 23 | response, 24 | async execute() { 25 | return (response.value = await proxy.execute(siteKey, { action })) 26 | }, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: Build document 2 | 3 | on: 4 | push: 5 | branches: 6 | - v3 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v5 13 | with: 14 | fetch-depth: 0 15 | - uses: pnpm/action-setup@v4 16 | with: 17 | run_install: false 18 | - uses: actions/setup-node@v6 19 | with: 20 | node-version: 22 21 | cache: pnpm 22 | - uses: actions4git/setup-git@v1 23 | - uses: moonrepo/setup-toolchain@v0 24 | - run: | 25 | git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY 26 | - name: Build and publish document 27 | run: moon run docs:publish 28 | env: 29 | NODE_OPTIONS: --max-old-space-size=8192 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: [bug] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Description 8 | description: Give a concise description of the problem 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: Minimal Reproducible Example 14 | description: 'Provide an example that reproduces the issue, either as a code snippet or a link to repo or StackBlitz.' 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: System info 20 | description: "Provide the output for `npx nuxi info` for Nuxt or `npx envinfo --system --npmPackages '{vite,@vitejs/*,nuxt,vue-recaptcha}' --binaries --browsers` for Vite project" 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | vue-recaptcha 2 | ============= 3 | [![CircleCI](https://circleci.com/gh/DanSnow/vue-recaptcha.svg?style=shield)](https://circleci.com/gh/DanSnow/vue-recaptcha) 4 | [![npm version](https://img.shields.io/npm/v/vue-recaptcha.svg?style=flat)](https://www.npmjs.com/package/vue-recaptcha) 5 | [![npm downloads](https://img.shields.io/npm/dm/vue-recaptcha.svg?style=flat)](https://www.npmjs.com/package/vue-recaptcha) 6 | 7 | Buy Me A Coffee 8 | 9 | Description 10 | ----------- 11 | 12 | **Notice:** This is the branch for vue-recaptcha v3 which is in development 13 | 14 | Google ReCAPTCHA component for vue. 15 | If you like this package, please leave a star on github. 16 | 17 | This version is for Vue 3 first 18 | 19 | Please see the document [here](https://dansnow.github.io/vue-recaptcha) 20 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [v3, main] 5 | pull_request: 6 | branches: [v3, main] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v5 13 | with: 14 | fetch-depth: 0 15 | - uses: pnpm/action-setup@v4 16 | with: 17 | run_install: false 18 | - uses: actions/setup-node@v6 19 | with: 20 | node-version: 22 21 | cache: pnpm 22 | - uses: moonrepo/setup-toolchain@v0 23 | - name: Install dependencies 24 | run: pnpm install 25 | - name: Run Playwright tests 26 | run: moon run vue-recaptcha:test-e2e 27 | - name: Run unit tests 28 | run: moon run :test-unit 29 | - uses: actions/upload-artifact@v5 30 | if: always() 31 | with: 32 | name: playwright-report 33 | path: playwright-report/ 34 | retention-days: 30 35 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/playwright/vue-recaptcha.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test' 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('/') 5 | }) 6 | 7 | test('Checkbox reCAPTCHA', async ({ page }) => { 8 | const recaptchaFrame = page.frameLocator('#checkbox [title*=reCAPTCHA]') 9 | 10 | const $recaptcha = recaptchaFrame.locator('.recaptcha-checkbox') 11 | await $recaptcha.click() 12 | await expect(page.getByTestId('checkbox-verify')).toHaveText('verified') 13 | }) 14 | 15 | test('Invisible reCAPTCHA', async ({ page }) => { 16 | // wait invisible reCAPTCHA loaded 17 | await page.waitForTimeout(1000) 18 | 19 | const button = page.locator('#invisible').getByRole('button') 20 | await button.click() 21 | await expect(page.getByTestId('v2-verify')).toHaveText('verified') 22 | }) 23 | 24 | test('v3 reCAPTCHA', async ({ page }) => { 25 | const button = page.locator('#v3').getByRole('button') 26 | await button.click() 27 | await expect(page.getByTestId('v3-verify')).toHaveText('verified') 28 | }) 29 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/script-manager/unhead.ts: -------------------------------------------------------------------------------- 1 | import type { ScriptLoaderOptions } from './common' 2 | import { useHead } from '@unhead/vue' 3 | import { defineScriptLoader, toQueryString } from './common' 4 | 5 | export const unheadScriptLoader = defineScriptLoader((options: ScriptLoaderOptions) => { 6 | return () => { 7 | useHead({ 8 | link: [ 9 | { 10 | key: 'vue-recaptcha-google', 11 | rel: 'preconnect', 12 | href: options.useRecaptchaNet ? 'https://www.recaptcha.net' : 'https://www.google.com', 13 | }, 14 | { 15 | key: 'vue-recaptcha-gstatic', 16 | rel: 'preconnect', 17 | href: 'https://www.gstatic.com', 18 | crossorigin: '', 19 | }, 20 | ], 21 | script: [ 22 | { 23 | key: 'vue-recaptcha', 24 | src: `${options.recaptchaApiURL}?${toQueryString(options.params)}`, 25 | async: true, 26 | defer: true, 27 | nonce: options.nonce, 28 | }, 29 | ], 30 | }) 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /packages/docs/content/3.composables/2.challenge-v3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useChallengeV3 3 | --- 4 | 5 | # useChallengeV3 :badge[v3] 6 | 7 | ## Description 8 | A composable to help you execute the reCAPTCHA v3 challenge 9 | 10 | ## Input 11 | 12 | - `action`: The action for reCAPTCHA v3 13 | 14 | ## Return 15 | 16 | ```ts 17 | export interface UseChallengeV3Return { 18 | /** 19 | * reCAPTCHA v3 response as ref 20 | */ 21 | response: Ref 22 | /** 23 | * Execute the challenge 24 | * @returns response for reCAPTCHA v3 25 | */ 26 | execute: () => Promise 27 | } 28 | ``` 29 | 30 | ## Usage 31 | 32 | ```vue 33 | 45 | 46 | 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/api.ts: -------------------------------------------------------------------------------- 1 | // components 2 | export { default as ChallengeV2 } from './components/ChallengeV2.vue' 3 | export { default as ChallengeV3 } from './components/ChallengeV3.vue' 4 | export { default as Checkbox } from './components/Checkbox.vue' 5 | export type { UseChallengeV2Input, UseChallengeV2Return } from './composables/challenge-v2' 6 | export { RecaptchaV2State, useChallengeV2 } from './composables/challenge-v2' 7 | export type { UseChallengeV3Return } from './composables/challenge-v3' 8 | export { useChallengeV3 } from './composables/challenge-v3' 9 | 10 | // composables 11 | export type { RecaptchaContext, RecaptchaContextKey, RecaptchaOptionsInput } from './composables/context' 12 | export { useRecaptchaContext, useRecaptchaProxy } from './composables/context' 13 | export { useRecaptchaProvider } from './composables/script-provider' 14 | 15 | // plugin 16 | export { createPlugin } from './plugin' 17 | 18 | // script loader 19 | export type { RecaptchaParams, ScriptLoaderFactory, ScriptLoaderOptions } from './script-manager/common' 20 | export { defineScriptLoader, toQueryString } from './script-manager/common' 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 DanSnow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/docs/components/content/VeeValidateInvisible.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 37 | -------------------------------------------------------------------------------- /packages/docs/content/1.guide/3.enterprise.md: -------------------------------------------------------------------------------- 1 | # Enterprise reCAPTCHA Support 2 | 3 | ::alert{type="warning"} 4 | Enterprise version support is experimental and may be changed in the future. 5 | :: 6 | 7 | Simply change your plugin import to the following: 8 | 9 | ```ts 10 | import VueRecaptchaPlugin from 'vue-recaptcha/enterprise' 11 | 12 | const app = createApp(App) 13 | app.use(VueRecaptchaPlugin) 14 | ``` 15 | 16 | ## Usage without unhead 17 | 18 | If you are not using `unhead` and want to use the enterprise version, you can use the following code: 19 | 20 | ```ts 21 | import VueRecaptchaPlugin from 'vue-recaptcha/enterprise-head' 22 | 23 | const app = createApp(App) 24 | app.use(VueRecaptchaPlugin) 25 | ``` 26 | 27 | ## Nuxt 28 | 29 | If you are using Nuxt, you can set the `recaptcha.enterprise` option to `true`: 30 | 31 | ```ts 32 | defineNuxtConfig({ 33 | modules: ['vue-recaptcha/nuxt'], 34 | runtimeConfig: { 35 | public: { 36 | recaptcha: { 37 | v2SiteKey: 'YOUR_V2_SITEKEY_HERE', 38 | v3SiteKey: 'YOUR_V3_SITEKEY_HERE', 39 | }, 40 | }, 41 | }, 42 | recaptcha: { 43 | enterprise: true, 44 | }, 45 | }) 46 | ``` 47 | -------------------------------------------------------------------------------- /packages/docs/content/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue-recaptcha document 3 | navigation: false 4 | layout: page 5 | --- 6 | 7 | ::block-hero 8 | --- 9 | cta: 10 | - Get Started 11 | - /guide 12 | secondary: 13 | - Open on GitHub → 14 | - https://github.com/DanSnow/vue-recaptcha 15 | snippet: yarn add vue-recaptcha@next 16 | --- 17 | 18 | #title 19 | vue-recaptcha 20 | 21 | #description 22 | Make Google's reCAPTCHA easy to use in Vue.js/Nuxt.js 23 | :: 24 | 25 | ::index-demo 26 | ::code-group 27 | ::code-block{label="Preview" preview} 28 | :recaptcha-checkbox 29 | :: 30 | 31 | ```vue [Code] 32 | 35 | ``` 36 | :: 37 | :: 38 | 39 | ::card-grid 40 | #title 41 | What's included 42 | #root 43 | :ellipsis 44 | #default 45 | ::card 46 | #title 47 | reCAPTCHA v2 48 | #description 49 | The "I'm not a robot" checkbox 50 | :: 51 | ::card 52 | #title 53 | reCAPTCHA v3 54 | #description 55 | Determinate if user is human without interrupt the user flow 56 | :: 57 | ::card 58 | #title 59 | Nuxt integration 60 | #description 61 | Use reCAPTCHA in Nuxt.js 62 | :: 63 | :: 64 | -------------------------------------------------------------------------------- /packages/playground-nuxt/README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3 Minimal Starter 2 | 3 | Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. 4 | 5 | ## Setup 6 | 7 | Make sure to install the dependencies: 8 | 9 | ```bash 10 | # npm 11 | npm install 12 | 13 | # pnpm 14 | pnpm install 15 | 16 | # yarn 17 | yarn install 18 | 19 | # bun 20 | bun install 21 | ``` 22 | 23 | ## Development Server 24 | 25 | Start the development server on `http://localhost:3000`: 26 | 27 | ```bash 28 | # npm 29 | npm run dev 30 | 31 | # pnpm 32 | pnpm run dev 33 | 34 | # yarn 35 | yarn dev 36 | 37 | # bun 38 | bun run dev 39 | ``` 40 | 41 | ## Production 42 | 43 | Build the application for production: 44 | 45 | ```bash 46 | # npm 47 | npm run build 48 | 49 | # pnpm 50 | pnpm run build 51 | 52 | # yarn 53 | yarn build 54 | 55 | # bun 56 | bun run build 57 | ``` 58 | 59 | Locally preview production build: 60 | 61 | ```bash 62 | # npm 63 | npm run preview 64 | 65 | # pnpm 66 | pnpm run preview 67 | 68 | # yarn 69 | yarn preview 70 | 71 | # bun 72 | bun run preview 73 | ``` 74 | 75 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. 76 | -------------------------------------------------------------------------------- /packages/docs/README.md: -------------------------------------------------------------------------------- 1 | # Docus Starter 2 | 3 | Starter template for [Docus](https://docus.dev). 4 | 5 | ## Clone 6 | 7 | Clone the repository (using `nuxi`): 8 | 9 | ```bash 10 | npx nuxi init -t themes/docus 11 | ``` 12 | 13 | ## Setup 14 | 15 | Install dependencies: 16 | 17 | ```bash 18 | yarn install 19 | ``` 20 | 21 | ## Development 22 | 23 | ```bash 24 | yarn dev 25 | ``` 26 | 27 | ## Edge Side Rendering 28 | 29 | Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compatible environments. 30 | 31 | Look at all the available presets [here](https://v3.nuxtjs.org/guide/deploy/presets). 32 | 33 | ```bash 34 | yarn build 35 | ``` 36 | 37 | ## Static Generation 38 | 39 | Use the `generate` command to build your application. 40 | 41 | The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. 42 | 43 | ```bash 44 | yarn generate 45 | ``` 46 | 47 | ## Preview build 48 | 49 | You might want to preview the result of your build locally, to do so, run the following command: 50 | 51 | ```bash 52 | yarn preview 53 | ``` 54 | 55 | --- 56 | 57 | For a detailed explanation of how things work, check out [Docus](https://docus.dev). 58 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/components/ChallengeV3.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 55 | -------------------------------------------------------------------------------- /packages/docs/content/1.guide/2.migration.md: -------------------------------------------------------------------------------- 1 | # Migration from v2 2 | 3 | ## 1. set up the plugin 4 | V3 need you to set up a Vue plugin before start using. 5 | 6 | ```ts 7 | import { VueRecaptchaPlugin } from 'vue-recaptcha' 8 | 9 | const app = createApp(App) 10 | app.use(VueRecaptchaPlugin, { 11 | v2SiteKey: 'YOUR_SITE_KEY_HERE' 12 | }) 13 | ``` 14 | 15 | ## 2. Provide reCAPTCHA script 16 | To load the reCAPTCHA script, you will need to call `useRecaptchaProvider` in your `app.vue` 17 | 18 | ```vue 19 | 24 | ``` 25 | 26 | ## 3. Use the components 27 | Now you can start using the components 28 | 29 | If you are using the "I'm not a robot" checkbox, please use the `` 30 | 31 | ```vue 32 | 35 | ``` 36 | 37 | You can read more at [here](../components/checkbox) 38 | 39 | If you are using the invisible reCAPTCHA, please use the `` 40 | 41 | ```vue 42 | 47 | ``` 48 | 49 | You can read more at [here](../components/challenge-v2) 50 | -------------------------------------------------------------------------------- /packages/docs/components/content/VeeValidateCheckbox.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 39 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/components/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 60 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/composables/proxy.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue-demi' 2 | import type { GRecaptcha, RecaptchaV2Options, WidgetID } from '../script-manager/common' 3 | import { recaptchaLoaded } from '../script-manager/common' 4 | import { invariant } from '../utils' 5 | 6 | export interface RecaptchaProxy { 7 | render: (ele: Element, options: RecaptchaV2Options) => Promise 8 | reset: (widgetId?: WidgetID | undefined) => void 9 | execute: ((siteKey: string, options: { action: string }) => Promise) & 10 | ((widgetId?: WidgetID | undefined) => void) 11 | } 12 | 13 | export function createRecaptchaProxy(isReady: Ref, getRecaptcha: () => GRecaptcha): RecaptchaProxy { 14 | function assertLoaded() { 15 | invariant(isReady.value, 'ReCAPTCHA is not loaded') 16 | } 17 | 18 | async function wait() { 19 | await recaptchaLoaded.promise 20 | isReady.value = true 21 | } 22 | 23 | return { 24 | async render(ele: Element, options: RecaptchaV2Options) { 25 | await wait() 26 | return getRecaptcha().render(ele, options) 27 | }, 28 | 29 | reset(widgetId?: WidgetID | undefined) { 30 | if (typeof widgetId === 'undefined') return 31 | 32 | assertLoaded() 33 | getRecaptcha().reset(widgetId) 34 | }, 35 | 36 | async execute(widgetId?: WidgetID | undefined | string, options?: { action: string }) { 37 | if (typeof widgetId === 'undefined') return 38 | 39 | await wait() 40 | return getRecaptcha().execute(widgetId, options as any) as any 41 | }, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/docs/content/2.components/1.checkbox.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Checkbox 3 | --- 4 | 5 | # Checkbox :badge[v2] 6 | 7 | Insert reCAPTCHA v2 checkbox 8 | 9 | ::code-group 10 | ::code-block{label="Preview" preview} 11 | :checkbox-demo 12 | :: 13 | 14 | ```vue [Code] 15 | 19 | 20 | 27 | ``` 28 | :: 29 | 30 | ## Props 31 | | **key** | **type** | **default** | **description** | 32 | |---------|-------------------------|-------------|---------------------------------| 33 | | `as` | string | `'div'` | What element to render | 34 | | `theme` | `'light' \| 'dark'` | `'light'` | Theme for reCAPTCHA checkbox | 35 | | `size` | `'normal' \| 'compact'` | `'normal'` | Size for the reCAPTCHA checkbox | 36 | 37 | ## `v-model` 38 | 39 | | **key** | **type** | **description** | 40 | |--------------|----------|----------------------------------| 41 | | `modelValue` | string | The reCAPTCHA response | 42 | | `widgetId` | string | Widget id for reCAPTCHA checkbox | 43 | 44 | ### About `modelValue` 45 | 46 | You can set `modelValue` to `null` or empty string to reset reCAPTCHA checkbox. 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "testing.automaticallyOpenPeekView": "never", 3 | // Enable the ESlint flat config support 4 | 5 | // Disable the default formatter, use eslint instead 6 | "prettier.enable": true, 7 | "editor.formatOnSave": true, 8 | 9 | "editor.defaultFormatter": "esbenp.prettier-vscode", 10 | 11 | "[javascript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[json]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "[jsonc]": { 18 | "editor.defaultFormatter": "esbenp.prettier-vscode" 19 | }, 20 | 21 | // Auto fix 22 | "editor.codeActionsOnSave": { 23 | "source.fixAll.eslint": "always", 24 | "source.organizeImports": "never" 25 | }, 26 | 27 | // Silent the stylistic rules in you IDE, but still auto fix them 28 | "eslint.rules.customizations": [ 29 | { "rule": "style/*", "severity": "off" }, 30 | { "rule": "format/*", "severity": "off" }, 31 | { "rule": "*-indent", "severity": "off" }, 32 | { "rule": "*-spacing", "severity": "off" }, 33 | { "rule": "*-spaces", "severity": "off" }, 34 | { "rule": "*-order", "severity": "off" }, 35 | { "rule": "*-dangle", "severity": "off" }, 36 | { "rule": "*-newline", "severity": "off" }, 37 | { "rule": "*quotes", "severity": "off" }, 38 | { "rule": "*semi", "severity": "off" } 39 | ], 40 | 41 | // Enable eslint for all supported languages 42 | "eslint.validate": [ 43 | "javascript", 44 | "javascriptreact", 45 | "typescript", 46 | "typescriptreact", 47 | "vue", 48 | "html", 49 | "markdown", 50 | "json", 51 | "jsonc", 52 | "yaml", 53 | "toml" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /packages/docs/moon.yml: -------------------------------------------------------------------------------- 1 | $schema: "https://moonrepo.dev/schemas/project.json" 2 | 3 | platform: node 4 | 5 | dependsOn: 6 | - vue-recaptcha 7 | 8 | tasks: 9 | dev: 10 | command: 11 | - nuxt 12 | - dev 13 | deps: 14 | - ^:build 15 | local: true 16 | generate: 17 | command: 18 | - nuxt 19 | - generate 20 | deps: 21 | - ^:build 22 | inputs: 23 | - content/** 24 | - components/** 25 | - layouts/** 26 | - public/** 27 | - app.config.ts 28 | - app.vue 29 | - nuxt.config.ts 30 | - tokens.config.ts 31 | test-unit: 32 | command: vitest 33 | deps: 34 | - generate 35 | inputs: 36 | - tests/** 37 | - vite.config.mts 38 | platform: node 39 | test: 40 | command: noop 41 | deps: 42 | - test-unit 43 | check-200-html: 44 | command: 45 | - test 46 | - -f 47 | - .output/public/200.html 48 | deps: 49 | - ~:generate 50 | inputs: 51 | - .output/public/200.html 52 | platform: system 53 | check-nuxt-dir: 54 | command: 55 | - test 56 | - -d 57 | - .output/public/_nuxt 58 | deps: 59 | - ~:generate 60 | inputs: 61 | - .output/public/_nuxt 62 | platform: system 63 | generate-with-nojekyll: 64 | command: 65 | - touch 66 | - .output/public/.nojekyll 67 | deps: 68 | - ~:check-200-html 69 | - ~:check-nuxt-dir 70 | outputs: 71 | - .output/public/.nojekyll 72 | platform: system 73 | publish: 74 | command: 75 | - gh-pages 76 | - -d 77 | - .output/public 78 | - -t 79 | deps: 80 | - ~:generate-with-nojekyll 81 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'vue-demi' 2 | import type { RecaptchaOptionsInput } from './composables/context' 3 | import type { GRecaptcha } from './script-manager/common' 4 | import type { RecaptchaPlugin } from './types' 5 | import { ref } from 'vue-demi' 6 | import { normalizeOptions, RecaptchaContextKey } from './composables/context' 7 | import { createRecaptchaProxy } from './composables/proxy' 8 | import { checkRecaptchaLoad, recaptchaLoaded } from './script-manager/common' 9 | import { createHeadRecaptcha } from './script-manager/head' 10 | import { warn } from './utils' 11 | 12 | export interface CreatePluginOptions { 13 | getRecaptcha?: () => GRecaptcha 14 | } 15 | 16 | export function createPlugin(plugins: RecaptchaPlugin[] = []): Plugin<[RecaptchaOptionsInput]> { 17 | const { getRecaptcha, scriptLoader }: Required = Object.assign( 18 | { 19 | scriptLoader: createHeadRecaptcha, 20 | getRecaptcha: () => window.grecaptcha, 21 | }, 22 | ...plugins, 23 | ) 24 | return { 25 | install(app, options) { 26 | const isReady = ref(false) 27 | 28 | async function waitLoaded() { 29 | await recaptchaLoaded.promise 30 | isReady.value = true 31 | } 32 | 33 | waitLoaded().catch((error) => warn('fail to load reCAPTCHA script', error)) 34 | checkRecaptchaLoad() 35 | 36 | const opt = normalizeOptions(options) 37 | 38 | app.provide(RecaptchaContextKey, { 39 | isReady, 40 | scriptInjected: false, 41 | proxy: createRecaptchaProxy(isReady, getRecaptcha), 42 | useScriptProvider: scriptLoader(opt.loaderOptions), 43 | options: opt, 44 | }) 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/playground-nuxt/app.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/linux,node 2 | 3 | ### Linux ### 4 | *~ 5 | 6 | # temporary files which can be created if a process still has a handle open of a deleted file 7 | .fuse_hidden* 8 | 9 | # KDE directory preferences 10 | .directory 11 | 12 | # Linux trash folder which might appear on any partition or disk 13 | .Trash-* 14 | 15 | 16 | ### Node ### 17 | # Logs 18 | logs 19 | *.log 20 | npm-debug.log* 21 | 22 | # Runtime data 23 | pids 24 | *.pid 25 | *.seed 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | 33 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (http://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directory 43 | node_modules 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | ## Output file 52 | lib/ 53 | 54 | reports 55 | ### yarn 56 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 57 | 58 | .yarn/* 59 | !.yarn/releases 60 | !.yarn/plugins 61 | !.yarn/sdks 62 | !.yarn/versions 63 | 64 | # if you are NOT using Zero-installs, then: 65 | # comment the following lines 66 | # .yarn/cache 67 | 68 | # and uncomment the following lines 69 | .pnp.* 70 | 71 | junit.xml 72 | ### yarn 73 | # https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored 74 | 75 | .yarn/* 76 | !.yarn/releases 77 | !.yarn/plugins 78 | !.yarn/sdks 79 | !.yarn/versions 80 | 81 | # if you are NOT using Zero-installs, then: 82 | # comment the following lines 83 | # !.yarn/cache 84 | 85 | # and uncomment the following lines 86 | .pnp.* 87 | 88 | dist 89 | package.tgz 90 | test-results 91 | playwright-report 92 | playwright/.cache 93 | 94 | # moon 95 | .moon/cache 96 | .moon/docker 97 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/example/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 53 | -------------------------------------------------------------------------------- /packages/playground-vite/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 53 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/script-manager/__tests__/head.spec.ts: -------------------------------------------------------------------------------- 1 | import type {} from 'typed-query-selector' 2 | import { cleanup, render } from '@testing-library/vue' 3 | import { afterEach, describe, expect, it } from 'vitest' 4 | import { defineComponent } from 'vue' 5 | import { createHeadRecaptcha } from '../head' 6 | 7 | describe('createHeadRecaptcha', () => { 8 | afterEach(() => { 9 | cleanup() 10 | }) 11 | 12 | it('should not add script if element with id "vue-recaptcha" already exists', () => { 13 | // Setup: Add a script element with id 'vue-recaptcha' to the document 14 | const existingScript = document.createElement('script') 15 | existingScript.id = 'vue-recaptcha' 16 | document.head.appendChild(existingScript) 17 | 18 | render( 19 | defineComponent(() => { 20 | createHeadRecaptcha({ 21 | recaptchaApiURL: 'https://example.com/recaptcha', 22 | params: { 23 | render: 'explicit', 24 | }, 25 | })() 26 | 27 | return () => null 28 | }), 29 | ) 30 | 31 | // Assert: No new script should be added 32 | const scripts = document.querySelectorAll('script') 33 | expect(scripts.length).toBe(1) 34 | 35 | // Cleanup 36 | document.head.removeChild(existingScript) 37 | }) 38 | 39 | it('should add script if element with id "vue-recaptcha" does not exist', () => { 40 | render( 41 | defineComponent(() => { 42 | createHeadRecaptcha({ 43 | recaptchaApiURL: 'https://example.com/recaptcha', 44 | params: { 45 | render: 'explicit', 46 | }, 47 | })() 48 | 49 | return () => null 50 | }), 51 | ) 52 | 53 | // Assert: A new script should be added 54 | const script = document.querySelector('script#vue-recaptcha') 55 | expect(script).not.toBeNull() 56 | expect(script?.src).toContain('https://example.com/recaptcha') 57 | 58 | // Cleanup 59 | if (script) document.head.removeChild(script) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import { defineConfig, devices } from '@playwright/test' 3 | import { isCI } from 'std-env' 4 | 5 | /** 6 | * Read environment variables from file. 7 | * https://github.com/motdotla/dotenv 8 | */ 9 | // require('dotenv').config(); 10 | 11 | /** 12 | * See https://playwright.dev/docs/test-configuration. 13 | */ 14 | export default defineConfig({ 15 | testDir: './playwright', 16 | /* Maximum time one test can run for. */ 17 | timeout: 30 * 1000, 18 | expect: { 19 | /** 20 | * Maximum time expect() should wait for the condition to be met. 21 | * For example in `await expect(locator).toHaveText();` 22 | */ 23 | timeout: 5000, 24 | }, 25 | /* Run tests in files in parallel */ 26 | fullyParallel: true, 27 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 28 | forbidOnly: !!isCI, 29 | /* Retry on CI only */ 30 | retries: isCI ? 2 : 0, 31 | /* Opt out of parallel tests on CI. */ 32 | workers: isCI ? 1 : undefined, 33 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 34 | reporter: 'html', 35 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 36 | use: { 37 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 38 | actionTimeout: 0, 39 | /* Base URL to use in actions like `await page.goto('/')`. */ 40 | // baseURL: 'http://localhost:3000', 41 | 42 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 43 | trace: 'on-first-retry', 44 | }, 45 | 46 | /* Configure projects for major browsers */ 47 | projects: [ 48 | { 49 | name: 'chromium', 50 | use: { 51 | ...devices['Desktop Chrome'], 52 | channel: process.env.CI ? 'chrome' : undefined, 53 | }, 54 | }, 55 | ], 56 | 57 | /* Run your local dev server before starting the tests */ 58 | webServer: { 59 | command: 'moon run dev -- --port 3456', 60 | port: 3456, 61 | }, 62 | }) 63 | -------------------------------------------------------------------------------- /packages/docs/content/2.components/3.challenge-v3.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ChallengeV3 3 | --- 4 | 5 | # ChallengeV3 :badge[v3] 6 | 7 | Create reCAPTCHA v3 challenge 8 | 9 | ::code-group 10 | ::code-block{label="Preview" preview} 11 | :challenge-v3-demo 12 | :: 13 | 14 | ```vue [Code] 15 | 18 | 19 | 26 | ``` 27 | :: 28 | 29 | ## Props 30 | | **key** | **type** | **default** | **description** | 31 | |---------------|----------|-------------|---------------------------------------------------------------------------------------------------------------------------------| 32 | | `action` | string | | The `action` for reCAPTCHA v3 | 33 | | `as` | string | `'div'` | What element to render | 34 | | `autoExecute` | boolean | `true` | Should vue-recaptcha execute challenge on click, if you set it to `false` you'll need to call the `execute` method in the slot | 35 | 36 | ## `v-model` 37 | 38 | | **key** | **type** | **description** | 39 | |--------------|----------|------------------------| 40 | | `modelValue` | string | The reCAPTCHA response | 41 | 42 | ## Slots 43 | 44 | ### default 45 | This is the position that usually place a your submit button. When click on the content, `ChallengeV3` will automatically execute the reCAPTCHA challenge by default 46 | 47 | #### Slot API 48 | 49 | ```typescript 50 | interface SlotApi { 51 | /** 52 | * reCAPTCHA v3 response 53 | */ 54 | response: string | undefined 55 | /** 56 | * execute reCAPTCHA v3 challenge 57 | */ 58 | execute: () => Promise 59 | } 60 | ``` 61 | -------------------------------------------------------------------------------- /packages/docs/content/4.advanced/0.customize-recaptcha-script-loading.md: -------------------------------------------------------------------------------- 1 | # Customize reCAPTCHA script loading 2 | 3 | By default vue-recaptcha will use `unhead` to load the reCAPTCHA script. 4 | 5 | If you need to completely control how to load the reCAPTCHA script. You can use `createPlugin` to create a plugin with your own `loadScript` function. 6 | 7 | ```ts 8 | import { onMounted } from 'vue' 9 | import { defineScriptLoader, toQueryString } from 'vue-recaptcha' 10 | 11 | // Define your own loadScript function 12 | const loadScript = defineScriptLoader((options) => { 13 | // This function will be called by useRecaptchaProvider 14 | return () => { 15 | onMounted(() => { 16 | // You can use any way to load the reCAPTCHA script 17 | const script = document.createElement('script') 18 | script.src = `${options.recaptchaApiURL}?${toQueryString(options.params)}` 19 | script.async = true 20 | script.defer = true 21 | if (options.nonce) 22 | script.nonce = options.nonce 23 | 24 | document.head.append(script) 25 | }) 26 | } 27 | }) 28 | 29 | const plugin = createPlugin(loadScript) 30 | ``` 31 | 32 | ## The `defineScriptLoader` function and `ScriptLoaderOptions` 33 | 34 | ```ts 35 | export interface RecaptchaParams { 36 | // You must pass these two value as query string to reCAPTCHA 37 | render: LiteralUnion<'explicit', string> 38 | onload: string 39 | 40 | // Below you can customize as you want 41 | hl?: string | undefined 42 | trustedtypes?: 'true' | undefined 43 | [k: string]: string | undefined 44 | } 45 | 46 | export interface ScriptLoaderOptions { 47 | /** 48 | * you can use recaptcha.net instead of google.com, if you set recaptchaApiURL, this option will be ignored 49 | */ 50 | useRecaptchaNet?: boolean 51 | /** 52 | * you can use your own recaptcha api url, if you set this option, useRecaptchaNet will be ignored 53 | */ 54 | recaptchaApiURL: string 55 | /** 56 | * nonce for script tag 57 | */ 58 | nonce?: string 59 | /** 60 | * params for recaptcha api 61 | */ 62 | params: RecaptchaParams 63 | } 64 | 65 | /** 66 | * Helper function for define your own loadScript function 67 | */ 68 | function defineScriptLoader(fn: ScriptLoaderFactory): NormalizedScriptLoaderFactory 69 | ``` 70 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/composables/component-v2.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue-demi' 2 | import type { WidgetID } from '../script-manager/common' 3 | import type { RecaptchaV2OptionsInput } from './challenge-v2' 4 | import { whenever } from '@vueuse/shared' 5 | import { computed, watch } from 'vue-demi' 6 | import { RecaptchaV2State, useChallengeV2 } from './challenge-v2' 7 | 8 | interface Emits { 9 | (event: 'load', widgetID: WidgetID): void 10 | (event: 'error', error: Error): void 11 | (event: 'expired', widgetID: WidgetID): void 12 | (event: 'success', response: string): void 13 | (event: 'update:widgetId', widgetID: WidgetID): void 14 | (event: 'update:modelValue', response: string | null): void 15 | } 16 | 17 | export interface UseComponentV2Return { 18 | root: Ref 19 | widgetID: Ref 20 | state: Ref 21 | isError: Ref 22 | isExpired: Ref 23 | isVerified: Ref 24 | reset: () => void 25 | execute: () => void 26 | } 27 | 28 | export function useComponentV2( 29 | options: RecaptchaV2OptionsInput | undefined, 30 | modelValue: Ref, 31 | emit: Emits, 32 | ) { 33 | const { root, state, widgetID, onError, onExpired, onVerify, reset, execute } = useChallengeV2({ 34 | options: options || {}, 35 | }) 36 | 37 | const isExpired = computed(() => state.value === RecaptchaV2State.Expired) 38 | const isError = computed(() => state.value === RecaptchaV2State.Error) 39 | const isVerified = computed(() => state.value === RecaptchaV2State.Verified) 40 | 41 | whenever(widgetID, (id) => { 42 | emit('load', id) 43 | emit('update:widgetId', id) 44 | }) 45 | 46 | watch(modelValue, (res, old) => { 47 | if (!res && old && !isExpired.value) callReset() 48 | }) 49 | 50 | onExpired(() => { 51 | emit('update:modelValue', null) 52 | emit('expired', widgetID.value!) 53 | }) 54 | 55 | onError((err) => { 56 | emit('error', err) 57 | }) 58 | 59 | onVerify((response) => { 60 | emit('success', response) 61 | emit('update:modelValue', response) 62 | }) 63 | 64 | return { root, widgetID, state, isError, isExpired, isVerified, reset: callReset, execute } 65 | 66 | function callReset() { 67 | reset() 68 | resetState() 69 | } 70 | 71 | function resetState() { 72 | emit('update:modelValue', null) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/composables/context.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey, Ref } from 'vue-demi' 2 | import type { RecaptchaParams, ScriptLoaderOptionsInput } from '../script-manager/common' 3 | import type { RecaptchaProxy } from './proxy' 4 | import { inject } from 'vue-demi' 5 | import { invariant, warn } from '../utils' 6 | 7 | export interface RecaptchaOptionsInput { 8 | v2SiteKey?: string | undefined 9 | v3SiteKey?: string | undefined 10 | loaderOptions?: ScriptLoaderOptionsInput & { params?: RecaptchaParams } 11 | } 12 | 13 | export interface RecaptchaOptions { 14 | v2SiteKey?: string | undefined 15 | v3SiteKey?: string | undefined 16 | loaderOptions: ScriptLoaderOptionsInput 17 | } 18 | 19 | export interface RecaptchaContext { 20 | isReady: Ref 21 | proxy: RecaptchaProxy 22 | scriptInjected: boolean 23 | useScriptProvider: () => void 24 | options: RecaptchaOptions 25 | } 26 | 27 | export const RecaptchaContextKey = 'vue-recaptcha-context' as unknown as InjectionKey 28 | 29 | export function useRecaptchaContext() { 30 | const context = inject(RecaptchaContextKey) 31 | 32 | if (!context) { 33 | warn('You may forget to `use` vue-recaptcha plugin') 34 | throw new Error('useRecaptcha() is called without provider.') 35 | } 36 | 37 | return context 38 | } 39 | 40 | export function useRecaptchaProxy() { 41 | const ctx = useRecaptchaContext() 42 | return ctx.proxy 43 | } 44 | 45 | export function useAssertV2SiteKey(): string { 46 | const ctx = useRecaptchaContext() 47 | invariant(ctx.options.v2SiteKey, 'Your config is not compatible with recaptcha v2, please provide v2SiteKey') 48 | return ctx.options.v2SiteKey 49 | } 50 | 51 | export function useAssertV3SiteKey(): string { 52 | const ctx = useRecaptchaContext() 53 | invariant(ctx.options.v3SiteKey, 'Your config is not compatible with recaptcha v3, please provide v3SiteKey') 54 | return ctx.options.v3SiteKey 55 | } 56 | 57 | export function normalizeOptions(input: RecaptchaOptionsInput): RecaptchaOptions { 58 | invariant( 59 | input.v2SiteKey || input.v3SiteKey, 60 | "You didn't pass v2SiteKey or v3SiteKey to plugin, which may be a mistake", 61 | ) 62 | 63 | return { 64 | ...input, 65 | loaderOptions: { 66 | ...input.loaderOptions, 67 | params: { 68 | ...input.loaderOptions?.params, 69 | render: input.v3SiteKey ?? 'explicit', 70 | }, 71 | }, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/nuxt.ts: -------------------------------------------------------------------------------- 1 | import type { RecaptchaOptionsInput } from './api' 2 | import { addComponent, addImports, addPlugin, createResolver, defineNuxtModule, useLogger, useNuxt } from '@nuxt/kit' 3 | 4 | const COMPONENTS = { 5 | RecaptchaCheckbox: 'Checkbox', 6 | RecaptchaChallengeV2: 'ChallengeV2', 7 | RecaptchaChallengeV3: 'ChallengeV3', 8 | } 9 | 10 | declare module '@nuxt/schema' { 11 | interface PublicRuntimeConfig { 12 | recaptcha: RecaptchaOptionsInput 13 | } 14 | } 15 | 16 | export interface ModuleOptions { 17 | enterprise: boolean 18 | installPlugin: boolean 19 | _globalComponent: boolean 20 | } 21 | 22 | export default defineNuxtModule({ 23 | meta: { 24 | name: 'vue-recaptcha', 25 | configKey: 'recaptcha', 26 | compatibility: { 27 | nuxt: '^3.0.0', 28 | }, 29 | }, 30 | defaults: { 31 | enterprise: false, 32 | installPlugin: true, 33 | _globalComponent: false, 34 | }, 35 | async setup(opt) { 36 | const logger = useLogger('vue-recaptcha') 37 | const nuxt = useNuxt() 38 | const resolver = createResolver(import.meta.url) 39 | nuxt.options.build.transpile.push(resolver.resolve('.'), 'vue-recaptcha') 40 | 41 | for (const [name, component] of Object.entries(COMPONENTS)) { 42 | await addComponent({ 43 | name, 44 | global: opt._globalComponent, 45 | filePath: resolver.resolve('.'), 46 | export: component, 47 | }) 48 | } 49 | 50 | addImports([ 51 | { 52 | from: resolver.resolve('.'), 53 | name: 'useRecaptchaContext', 54 | }, 55 | { 56 | from: resolver.resolve('.'), 57 | name: 'useRecaptchaProxy', 58 | }, 59 | { 60 | from: resolver.resolve('.'), 61 | name: 'useChallengeV2', 62 | }, 63 | { 64 | from: resolver.resolve('.'), 65 | name: 'useChallengeV3', 66 | }, 67 | { 68 | from: resolver.resolve('.'), 69 | name: 'useRecaptchaProvider', 70 | }, 71 | ]) 72 | 73 | if (!opt.installPlugin) { 74 | if (opt.enterprise) logger.warn('`enterprise` option is ignored when `installPlugin` is false') 75 | 76 | return 77 | } 78 | 79 | if (opt.enterprise) { 80 | addPlugin({ 81 | src: resolver.resolve('./nuxt-enterprise-plugin'), 82 | }) 83 | } else { 84 | addPlugin({ 85 | src: resolver.resolve('./nuxt-plugin'), 86 | }) 87 | } 88 | }, 89 | }) 90 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/components/ChallengeV2.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 107 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-recaptcha", 3 | "type": "module", 4 | "version": "3.0.0-alpha.6", 5 | "description": "ReCAPTCHA vue component", 6 | "author": "DanSnow", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/DanSnow/vue-recaptcha.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/DanSnow/vue-recaptcha/issues" 14 | }, 15 | "keywords": [ 16 | "vue", 17 | "component", 18 | "vue-component", 19 | "google-recaptcha", 20 | "google-recaptcha-v3", 21 | "recaptcha", 22 | "recaptcha-v3", 23 | "gcaptcha", 24 | "captcha", 25 | "nuxt", 26 | "nuxt3", 27 | "nuxt-3" 28 | ], 29 | "exports": { 30 | ".": "./dist/index.js", 31 | "./nuxt": "./dist/nuxt.js", 32 | "./nuxt-enterprise-plugin": "./dist/nuxt-enterprise-plugin.js", 33 | "./nuxt-plugin": "./dist/nuxt-plugin.js", 34 | "./unhead": "./dist/unhead.js", 35 | "./package.json": "./package.json" 36 | }, 37 | "main": "./dist/index.js", 38 | "module": "./dist/index.js", 39 | "types": "./dist/index.d.ts", 40 | "files": [ 41 | "dist" 42 | ], 43 | "scripts": { 44 | "prepack": "moon run --no-actions build" 45 | }, 46 | "workspaces": [ 47 | ".", 48 | "docs" 49 | ], 50 | "peerDependencies": { 51 | "vue": "^3.0.0" 52 | }, 53 | "peerDependenciesMeta": { 54 | "@vue/composition-api": { 55 | "optional": true 56 | } 57 | }, 58 | "dependencies": { 59 | "@nuxt/kit": "^3.9.1", 60 | "@nuxt/schema": "^3.9.1", 61 | "@vueuse/shared": "^14.0.0", 62 | "defu": "^6.1.4", 63 | "p-defer": "^4.0.1", 64 | "std-env": "^3.9.0", 65 | "type-fest": "^5.0.0", 66 | "vue-demi": "^0.14.10" 67 | }, 68 | "devDependencies": { 69 | "@antfu/eslint-config": "6.7.1", 70 | "@playwright/test": "1.57.0", 71 | "@testing-library/dom": "10.4.1", 72 | "@testing-library/vue": "8.1.0", 73 | "@unhead/vue": "2.0.19", 74 | "@vitejs/plugin-vue": "6.0.3", 75 | "@vue/compiler-dom": "3.5.26", 76 | "@vue/composition-api": "1.7.2", 77 | "@vue/test-utils": "2.4.6", 78 | "eslint": "9.39.2", 79 | "eslint-config-prettier": "10.1.8", 80 | "eslint-plugin-prettier": "5.5.4", 81 | "happy-dom": "20.0.11", 82 | "husky": "9.1.7", 83 | "lint-staged": "16.2.7", 84 | "nuxi": "3.31.3", 85 | "pinst": "3.0.0", 86 | "prettier": "3.7.4", 87 | "publint": "0.3.16", 88 | "tsdown": "0.18.1", 89 | "typescript": "5.9.3", 90 | "unplugin-vue": "7.1.0", 91 | "vite": "7.3.0", 92 | "vitest": "3.2.4", 93 | "vue": "3.5.26", 94 | "vue-sfc-transformer": "0.1.17", 95 | "vue-tsc": "3.1.8" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/docs/content/1.guide/1.nuxt.md: -------------------------------------------------------------------------------- 1 | # Nuxt 2 | 3 | ::alert{type="warning"} 4 | When using vue-recaptcha in Nuxt, please try to utilize Nuxt's auto import feature as much as possible to avoid potential bugs. 5 | :: 6 | 7 | vue-recaptcha supports Nuxt.js out of the box. 8 | 9 | ```ts 10 | defineNuxtConfig({ 11 | modules: ['vue-recaptcha/nuxt'], 12 | }) 13 | ``` 14 | 15 | ::alert{type="danger"} 16 | Please noticed that to prevent from conflict with your components, vue-recaptcha's Nuxt integration has renaming the following components 17 | :: 18 | 19 | | **Original name** | **Renamed to** | 20 | |-------------------|------------------------| 21 | | `Checkbox` | `RecaptchaCheckbox` | 22 | | `ChallengeV2` | `RecaptchaChallengeV2` | 23 | | `ChallengeV3` | `RecaptchaChallengeV3` | 24 | 25 | ## Options 26 | 27 | You can pass options to the module by adding a `recaptcha` key to your `nuxt.config.js` file in `runtimeConfig.public`. 28 | 29 | ```ts 30 | defineNuxtConfig({ 31 | modules: ['vue-recaptcha/nuxt'], 32 | runtimeConfig: { 33 | public: { 34 | recaptcha: { 35 | v2SiteKey: 'YOUR_V2_SITEKEY_HERE', 36 | v3SiteKey: 'YOUR_V3_SITEKEY_HERE', 37 | }, 38 | }, 39 | }, 40 | }) 41 | ``` 42 | 43 | ## Enterprise 44 | 45 | If you are using reCAPTCHA Enterprise, you can use the `recaptcha` key in `nuxt.config` to enable the enterprise version support. 46 | 47 | ::alert{type="warning"} 48 | Enterprise version support is experimental and may be changed in the future. 49 | :: 50 | 51 | ```ts 52 | defineNuxtConfig({ 53 | modules: ['vue-recaptcha/nuxt'], 54 | runtimeConfig: { 55 | public: { 56 | recaptcha: { 57 | v2SiteKey: 'YOUR_V2_SITEKEY_HERE', 58 | v3SiteKey: 'YOUR_V3_SITEKEY_HERE', 59 | }, 60 | }, 61 | }, 62 | recaptcha: { 63 | enterprise: true, 64 | }, 65 | }) 66 | ``` 67 | 68 | ## Manually Install Plugin 69 | 70 | If you want to take full control of the plugin, you can manually install the plugin by adding the following code to your `nuxt.config.js` file. 71 | 72 | ```ts 73 | defineNuxtConfig({ 74 | modules: ['vue-recaptcha/nuxt'], 75 | runtimeConfig: { 76 | public: { 77 | recaptcha: { 78 | v2SiteKey: 'YOUR_V2_SITEKEY_HERE', 79 | v3SiteKey: 'YOUR_V3_SITEKEY_HERE', 80 | }, 81 | }, 82 | }, 83 | recaptcha: { 84 | plugin: false, 85 | }, 86 | }) 87 | ``` 88 | 89 | Then you will need to manually set up the plugin by creating a file named `recaptcha.ts` in your `plugins` directory. 90 | 91 | ```ts 92 | import VueRecaptchaPlugin from 'vue-recaptcha' 93 | 94 | export default defineNuxtPlugin(({ vueApp }) => { 95 | const { 96 | public: { recaptcha }, 97 | } = useRuntimeConfig() 98 | vueApp.use(VueRecaptchaPlugin, recaptcha) 99 | }) 100 | ``` 101 | -------------------------------------------------------------------------------- /packages/docs/content/5.integration/0.vee-validation.md: -------------------------------------------------------------------------------- 1 | # Vee Validation 2 | 3 | [Vee Validate](https://vee-validate.logaretm.com/v4/) is a plugin for form validation. 4 | 5 | ### Confirm user has clicked the checkbox before submit 6 | 7 | ::code-group 8 | ::code-block{label="Preview" preview} 9 | ::vee-validate-checkbox 10 | :: 11 | 12 | ```vue [Code] 13 | 31 | 32 | 53 | ``` 54 | :: 55 | 56 | ### Execute challenge when user submit form 57 | 58 | ::code-group 59 | ::code-block{label="Preview" preview} 60 | ::vee-validate-invisible 61 | :: 62 | 63 | ```vue [Code] 64 | 84 | 85 | 102 | ``` 103 | :: 104 | -------------------------------------------------------------------------------- /packages/docs/content/1.guide/0.index.md: -------------------------------------------------------------------------------- 1 | # Get started 2 | 3 | ::alert 4 | From vue-recaptcha v2? v3 is a complete rewrite, please check out [migration guide](./guide/migration) 5 | :: 6 | 7 | Let's get started with vue-recaptcha. 8 | 9 | # What is vue-recaptcha 10 | 11 | vue-recaptcha is just a wrapper to provide an easier interface for [Google's reCAPTCHA][recaptcha] 12 | 13 | ::list{type="success"} 14 | - component that is easy to use 15 | - composable to integrate into your own app 16 | :: 17 | 18 | vue-recaptcha is not for: 19 | ::list{type="danger"} 20 | - customize reCAPTCHA UI 21 | - provide features that is not implemented by Google reCATPCHA, like change language on the fly 22 | :: 23 | 24 | [recaptcha]: https://developers.google.com/recaptcha/ 25 | 26 | ## Installation 27 | 28 | ::code-group 29 | ::code-block{label="Yarn"} 30 | :terminal{content="yarn add vue-recaptcha@next"} 31 | :: 32 | ::code-block{label="NPM"} 33 | :terminal{content="npm install vue-recaptcha@next"} 34 | :: 35 | ::code-block{label="PNPM"} 36 | :terminal{content="pnpm add vue-recaptcha@next"} 37 | :: 38 | :: 39 | 40 | ## Setup vue-recaptcha plugin 41 | 42 | ::alert{type="warning"} 43 | By default, vue-recaptcha use `@unhead/vue` to inject the reCAPTCHA script, if you are not using `@unhead/vue` or `@vueuse/head`, please check out [Usage without unhead](#usage-without-unhead) 44 | :: 45 | 46 | ```ts 47 | import { VueRecaptchaPlugin } from 'vue-recaptcha' 48 | 49 | const app = createApp(App) 50 | app.use(VueRecaptchaPlugin, { 51 | v2SiteKey: 'YOUR_V2_SITEKEY_HERE', 52 | v3SiteKey: 'YOUR_V3_SITEKEY_HERE', 53 | }) 54 | ``` 55 | 56 | Please replace `YOUR_V2_SITEKEY_HERE` and `YOUR_V3_SITEKEY_HERE` with your keys. If you don't have one, please go to [here](https://www.google.com/recaptcha/admin) and apply one 57 | 58 | Not both of the sitekey is required, if you only need reCAPTCHA v2, just provide the `v2SiteKey` 59 | 60 | In this document, if you see :badge[v2] which means you'll need to pass `v2SiteKey` and :badge[v3] means you'll need `v3SiteKey` 61 | 62 | :alert[If you did't provide the corresponding site key, you'll get a runtime error]{type="warning"} 63 | 64 | ## Provide reCAPTCHA script 65 | 66 | To load the reCAPTCHA script, you will need to call `useRecaptchaProvider` in your `app.vue` 67 | 68 | ```vue 69 | 74 | 75 | 80 | ``` 81 | 82 | That's all, now you can start using vue-recaptcha. Please see [components](./components) for quick introduce for all of the components 83 | 84 | :button-link[Go to component overview]{href="./components"} 85 | 86 | ## Usage without unhead 87 | 88 | If you are not using `@unhead/vue` or `@vueuse/head`, you can simply import the plugin from `vue-recaptcha/head` 89 | 90 | ```ts 91 | import { VueRecaptchaPlugin } from 'vue-recaptcha/head' 92 | 93 | const app = createApp(App) 94 | app.use(VueRecaptchaPlugin, { 95 | v2SiteKey: 'YOUR_V2_SITEKEY_HERE', 96 | v3SiteKey: 'YOUR_V3_SITEKEY_HERE', 97 | }) 98 | ``` 99 | 100 | With this version, it will inject the reCAPTCHA script by simply creating a `script` tag in document `head` 101 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/composables/challenge-v2.ts: -------------------------------------------------------------------------------- 1 | import type { EventHookOn, MaybeRefOrGetter } from '@vueuse/shared' 2 | import type { Ref } from 'vue-demi' 3 | import type { RecaptchaV2CheckboxOptions, RecaptchaV2InvisibleOptions, WidgetID } from '../script-manager/common' 4 | import { createEventHook, toRef, whenever } from '@vueuse/shared' 5 | import { ref } from 'vue-demi' 6 | import { useAssertV2SiteKey, useRecaptchaProxy } from './context' 7 | 8 | type OmitKeys = 'sitekey' | 'callback' | 'expired-callback' | 'error-callback' 9 | 10 | export type RecaptchaV2CheckboxOptionsInput = Omit 11 | export type RecaptchaV2InvisibleOptionsInput = Omit 12 | export type RecaptchaV2OptionsInput = RecaptchaV2CheckboxOptionsInput | RecaptchaV2InvisibleOptionsInput 13 | 14 | export interface UseChallengeV2Input { 15 | /** 16 | * root to mount reCAPTCHA 17 | */ 18 | root?: MaybeRefOrGetter 19 | /** 20 | * Option that pass to reCAPTCHA render 21 | */ 22 | options?: RecaptchaV2OptionsInput 23 | } 24 | 25 | export enum RecaptchaV2State { 26 | Init = 'init', 27 | Verified = 'verified', 28 | Expired = 'expired', 29 | Error = 'error', 30 | } 31 | 32 | export interface UseChallengeV2Return { 33 | /** 34 | * root element ref to mount reCAPTCHA 35 | */ 36 | root: Ref 37 | /** 38 | * reCAPTCHA widget id 39 | */ 40 | widgetID: Ref 41 | 42 | /** 43 | * state of reCAPTCHA 44 | */ 45 | state: Ref 46 | 47 | /** 48 | * the verified event 49 | */ 50 | onVerify: EventHookOn 51 | /** 52 | * the expired event 53 | */ 54 | onExpired: EventHookOn 55 | /** 56 | * the error event 57 | */ 58 | onError: EventHookOn 59 | 60 | /** 61 | * execute the challenge 62 | */ 63 | execute: () => void 64 | /** 65 | * reset reCAPTCHA 66 | */ 67 | reset: () => void 68 | } 69 | 70 | export function useChallengeV2({ root = ref(), options = {} }: UseChallengeV2Input): UseChallengeV2Return { 71 | const siteKey = useAssertV2SiteKey() 72 | const widgetID = ref() 73 | const proxy = useRecaptchaProxy() 74 | const verify = createEventHook() 75 | const expired = createEventHook() 76 | const error = createEventHook() 77 | const rootRef = toRef(root) 78 | const state = ref(RecaptchaV2State.Init) 79 | 80 | whenever(rootRef, async (el) => { 81 | const id = await proxy.render(el, { 82 | ...options, 83 | sitekey: siteKey, 84 | callback: verify.trigger, 85 | 'expired-callback': expired.trigger, 86 | 'error-callback': error.trigger, 87 | }) 88 | widgetID.value = id 89 | }) 90 | 91 | verify.on(() => { 92 | state.value = RecaptchaV2State.Verified 93 | }) 94 | 95 | expired.on(() => { 96 | state.value = RecaptchaV2State.Expired 97 | }) 98 | 99 | error.on(() => { 100 | state.value = RecaptchaV2State.Error 101 | }) 102 | 103 | return { 104 | root: rootRef, 105 | widgetID, 106 | execute() { 107 | if (typeof widgetID.value !== 'undefined') proxy.execute(widgetID.value) 108 | }, 109 | reset() { 110 | state.value = RecaptchaV2State.Init 111 | if (typeof widgetID.value !== 'undefined') proxy.reset(widgetID.value) 112 | }, 113 | state, 114 | onVerify: verify.on, 115 | onExpired: expired.on, 116 | onError: error.on, 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /packages/docs/content/3.composables/1.challenge-v2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: useChallengeV2 3 | --- 4 | 5 | # useChallengeV2 :badge[v2] 6 | 7 | ## Description 8 | A composable to help you mount the reCAPTCHA checkbox or invisible reCAPTCHA 9 | 10 | ## Input 11 | 12 | ```ts 13 | interface RecaptchaV2CheckboxOptionsInput { 14 | theme?: 'dark' | 'light' 15 | size?: 'compact' | 'normal' 16 | tabindex?: string 17 | } 18 | 19 | interface RecaptchaV2InvisibleOptionsInput { 20 | badge?: 'bottomright' | 'bottomleft' | 'inline' 21 | size: 'invisible' 22 | tabindex?: string 23 | } 24 | 25 | export interface UseChallengeV2Input { 26 | /** 27 | * root to mount reCAPTCHA 28 | */ 29 | root?: MaybeComputedRef 30 | /** 31 | * Option that pass to reCAPTCHA render 32 | */ 33 | options?: RecaptchaV2OptionsInput 34 | } 35 | ``` 36 | 37 | ## Return 38 | 39 | ```ts 40 | export interface UseChallengeV2Return { 41 | /** 42 | * root element ref to mount reCAPTCHA 43 | */ 44 | root: Ref 45 | /** 46 | * reCAPTCHA widget id 47 | */ 48 | widgetID: Ref 49 | 50 | /** 51 | * state of reCAPTCHA 52 | */ 53 | state: Ref 54 | 55 | /** 56 | * the verified event 57 | */ 58 | onVerify: EventHookOn 59 | /** 60 | * the expired event 61 | */ 62 | onExpired: EventHookOn 63 | /** 64 | * the error event 65 | */ 66 | onError: EventHookOn 67 | 68 | /** 69 | * execute the challenge 70 | */ 71 | execute: () => void 72 | /** 73 | * reset reCAPTCHA 74 | */ 75 | reset: () => void 76 | } 77 | ``` 78 | 79 | About the `state` please see [here](../components/challenge-v2#recaptchav2state) for more details 80 | 81 | Please see [reCAPTCHA docs](https://developers.google.com/recaptcha/docs/display#render_param) for more information about the options. 82 | 83 | ## About the visible Badge for invisible reCAPTCHA 84 | 85 | You should be able to see the reCAPTCHA badge in the bottom right corner of this page. It is a mark displayed by Google when you use invisible reCAPTCHA. You can add the following CSS code to hide it. 86 | 87 | ```css 88 | .grecaptcha-badge { visibility: hidden; } 89 | ``` 90 | 91 | Please note that, according to Google's requirements, you will need to explicitly state that you are using reCAPTCHA. For more information, you can refer to this [link](https://developers.google.com/recaptcha/docs/faq#id-like-to-hide-the-recaptcha-badge.-what-is-allowed) 92 | 93 | ## Examples 94 | 95 | ### Checkbox 96 | 97 | ```vue 98 | 113 | 114 | 117 | ``` 118 | 119 | ### Invisible reCAPTCHA 120 | 121 | ```vue 122 | 137 | 138 | 147 | ``` 148 | -------------------------------------------------------------------------------- /packages/docs/content/2.components/2.challenge-v2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ChallengeV2 3 | --- 4 | 5 | # ChallengeV2 :badge[v2] 6 | 7 | Create invisible reCAPTCHA v2 8 | 9 | ::code-group 10 | ::code-block{label="Preview" preview} 11 | :challenge-v2-demo 12 | :: 13 | 14 | ```vue [Code] 15 | 27 | 28 | 33 | ``` 34 | :: 35 | 36 | ## Props 37 | | **key** | **type** | **default** | **description** | 38 | |---------------|-------------------------------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------| 39 | | `as` | string | `'div'` | What element to render | 40 | | `badge` | `'bottomright' | 'bottomleft' | 'inline'` | `'bottom right'` | Control the position of the badge that display the info about invisible reCAPTCHA | 41 | | `autoExecute` | boolean | `true` | Should vue-recaptcha execute challenge on click, if you set it to `false` you'll need to call the `execute` method in the slot | 42 | 43 | ## `v-model` 44 | 45 | | **key** | **type** | **description** | 46 | |--------------|----------|----------------------------------| 47 | | `modelValue` | string | The reCAPTCHA response | 48 | | `widgetId` | string | Widget id for reCAPTCHA checkbox | 49 | 50 | ### About `modelValue` 51 | 52 | You can set `modelValue` to `null` or empty string to reset reCAPTCHA checkbox. 53 | 54 | ## About the visible Badge for invisible reCAPTCHA 55 | 56 | You should be able to see the reCAPTCHA badge in the bottom right corner of this page. It is a mark displayed by Google when you use invisible reCAPTCHA. You can add the following CSS code to hide it. 57 | 58 | ```css 59 | .grecaptcha-badge { visibility: hidden; } 60 | ``` 61 | 62 | Please note that, according to Google's requirements, you will need to explicitly state that you are using reCAPTCHA. For more information, you can refer to this [link](https://developers.google.com/recaptcha/docs/faq#id-like-to-hide-the-recaptcha-badge.-what-is-allowed) 63 | 64 | ## Slots 65 | 66 | ### default 67 | This is the position that usually place a your submit button. When click on the content, `ChallengeV2` will automatically execute the reCAPTCHA challenge by default 68 | 69 | #### Slot API 70 | 71 | ```typescript 72 | interface SlotApi { 73 | /** 74 | * widget id 75 | */ 76 | widgetId: WidgetID | undefined 77 | /** 78 | * reCAPTCHA state 79 | */ 80 | state: RecaptchaV2State 81 | 82 | isError: boolean 83 | isExpired: boolean 84 | isVerified: boolean 85 | 86 | /** 87 | * reset reCAPTCHA 88 | */ 89 | reset: () => void 90 | /** 91 | * execute challenge 92 | */ 93 | execute: () => void 94 | } 95 | ``` 96 | 97 | ### `RecaptchaV2State` 98 | 99 | ```ts 100 | export enum RecaptchaV2State { 101 | Init = 'init', 102 | Verified = 'verified', 103 | Expired = 'expired', 104 | Error = 'error', 105 | } 106 | ``` 107 | 108 | ::mermaid 109 | flowchart TD 110 | Init -->|Challenge| Verified 111 | Init -->|Challenge| Error 112 | Verified -->|Challenge expired| Expired 113 | Error -->|Reset| Init 114 | Verified -->|Reset| Init 115 | Expired -->|Reset| Init 116 | :: 117 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/src/script-manager/common.ts: -------------------------------------------------------------------------------- 1 | import type { LiteralUnion, Tagged } from 'type-fest' 2 | import defu from 'defu' 3 | import pDefer from 'p-defer' 4 | import { warn } from '../utils' 5 | 6 | export type RecaptchaCallback = '__vueRecaptchaLoaded' 7 | 8 | export interface RecaptchaV2CommonOptions { 9 | sitekey: string 10 | tabindex?: string 11 | callback?: (response: string) => void 12 | 'expired-callback'?: () => void 13 | 'error-callback'?: (error: Error) => void 14 | } 15 | 16 | export interface RecaptchaV2CheckboxOptions extends RecaptchaV2CommonOptions { 17 | theme?: 'dark' | 'light' 18 | size?: 'compact' | 'normal' 19 | } 20 | 21 | export interface RecaptchaV2InvisibleOptions extends RecaptchaV2CommonOptions { 22 | size: 'invisible' 23 | badge?: 'bottomright' | 'bottomleft' | 'inline' 24 | } 25 | 26 | export type RecaptchaV2Options = RecaptchaV2CheckboxOptions | RecaptchaV2InvisibleOptions 27 | 28 | export type WidgetID = Tagged 29 | 30 | export interface GRecaptcha { 31 | render: (ele: Element, options: RecaptchaV2Options) => WidgetID 32 | reset: (widgetId: WidgetID) => void 33 | execute: ((widgetId: WidgetID) => void) & ((siteKey: string, options: { action: string }) => Promise) 34 | } 35 | 36 | declare global { 37 | interface Window { 38 | grecaptcha: GRecaptcha & { enterprise?: GRecaptcha } 39 | __vueRecaptchaLoaded: () => void 40 | } 41 | } 42 | 43 | export interface RecaptchaParams { 44 | render: LiteralUnion<'explicit', string> 45 | hl?: string | undefined 46 | trustedtypes?: 'true' | undefined 47 | onload?: RecaptchaCallback 48 | [k: string]: string | undefined 49 | } 50 | 51 | export interface ScriptLoaderOptionsInput { 52 | useRecaptchaNet?: boolean 53 | recaptchaApiURL?: string 54 | nonce?: string 55 | params: RecaptchaParams 56 | } 57 | 58 | export interface ScriptLoaderOptions { 59 | /** 60 | * you can use recaptcha.net instead of google.com, if you set recaptchaApiURL, this option will be ignored 61 | */ 62 | useRecaptchaNet?: boolean 63 | /** 64 | * you can use your own recaptcha api url, if you set this option, useRecaptchaNet will be ignored 65 | */ 66 | recaptchaApiURL: string 67 | /** 68 | * nonce for script tag 69 | */ 70 | nonce?: string 71 | /** 72 | * params for recaptcha api 73 | */ 74 | params: RecaptchaParams 75 | } 76 | 77 | export interface ScriptLoaderFactory { 78 | (options: ScriptLoaderOptions): () => void 79 | } 80 | 81 | export interface NormalizedScriptLoaderFactory { 82 | (options: ScriptLoaderOptionsInput): () => void 83 | } 84 | 85 | /** 86 | * Helper function for define your own loadScript function 87 | */ 88 | export function defineScriptLoader(fn: ScriptLoaderFactory): NormalizedScriptLoaderFactory { 89 | return (options: ScriptLoaderOptionsInput) => { 90 | return fn(normalizeScriptLoaderOptions(options)) 91 | } 92 | } 93 | 94 | export function normalizeScriptLoaderOptions(options: ScriptLoaderOptionsInput): ScriptLoaderOptions { 95 | return { 96 | ...options, 97 | recaptchaApiURL: 98 | options.recaptchaApiURL ?? 99 | (options.useRecaptchaNet 100 | ? 'https://www.recaptcha.net/recaptcha/api.js' 101 | : 'https://www.google.com/recaptcha/api.js'), 102 | } 103 | } 104 | 105 | export const recaptchaLoaded = pDefer() 106 | const ONLOAD_KEY: RecaptchaCallback = '__vueRecaptchaLoaded' as const 107 | 108 | if (typeof window !== 'undefined') { 109 | window[ONLOAD_KEY] = () => { 110 | recaptchaLoaded.resolve() 111 | } 112 | } 113 | 114 | export function toQueryString(params: RecaptchaParams) { 115 | return new URLSearchParams(normalizeParams(params)).toString() 116 | } 117 | 118 | export function normalizeParams(raw: RecaptchaParams): string[][] { 119 | const params = defu(raw, { onload: ONLOAD_KEY, render: 'explicit' }) 120 | if (params.render === 'onload') { 121 | warn('passing `onload` as `render` param is not allowed') 122 | params.render = 'explicit' 123 | } 124 | 125 | if (params.onload !== ONLOAD_KEY) { 126 | warn('passing `onload` param with other value is not allowed') 127 | params.onload = ONLOAD_KEY 128 | } 129 | 130 | return toStringPair(params) 131 | } 132 | 133 | export function toStringPair(params: RecaptchaParams): string[][] { 134 | return Object.entries(params).filter((pair): pair is [string, string] => typeof pair[1] === 'string') 135 | } 136 | 137 | export function checkRecaptchaLoad() { 138 | if (typeof window === 'undefined') return false 139 | 140 | const isLoaded = Object.hasOwn(window, 'grecaptcha') && Object.hasOwn(window.grecaptcha, 'execute') 141 | if (isLoaded) recaptchaLoaded.resolve() 142 | 143 | return isLoaded 144 | } 145 | -------------------------------------------------------------------------------- /packages/docs/public/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /packages/docs/public/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /packages/playground-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | "lib": [ 16 | "DOM", 17 | "ES2022" 18 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 19 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 25 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 28 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 29 | 30 | /* Modules */ 31 | "module": "ES2020" /* Specify what module code is generated. */, 32 | // "rootDir": "./", /* Specify the root folder within your source files. */ 33 | "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 34 | 35 | /* Type Checking */ 36 | "strict": true /* Enable all strict type-checking options. */, 37 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 38 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 39 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 40 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 41 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 42 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 43 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 44 | // "resolveJsonModule": true, /* Enable importing .json files. */ 45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 46 | 47 | /* JavaScript Support */ 48 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 49 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 50 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 51 | 52 | /* Emit */ 53 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 54 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 56 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 67 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 68 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 69 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 70 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 71 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 72 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 73 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 74 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 75 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 76 | 77 | /* Interop Constraints */ 78 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 83 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 84 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 85 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 86 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 87 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 88 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 89 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 90 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 91 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 92 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 93 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 94 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 95 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 96 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 97 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 98 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 99 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 100 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 101 | 102 | /* Completeness */ 103 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 104 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 105 | }, 106 | "include": ["./src/**/*.ts", "./src/**/*.vue"], 107 | "exclude": ["./dist/**/*"] 108 | } 109 | -------------------------------------------------------------------------------- /packages/vue-recaptcha/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | "lib": [ 16 | "DOM", 17 | "ES2022" 18 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 19 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 25 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 28 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 29 | 30 | /* Modules */ 31 | "module": "ES2020" /* Specify what module code is generated. */, 32 | // "rootDir": "./", /* Specify the root folder within your source files. */ 33 | "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 34 | 35 | /* Type Checking */ 36 | "strict": true /* Enable all strict type-checking options. */, 37 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 38 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 39 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 40 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 41 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 42 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 43 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 44 | // "resolveJsonModule": true, /* Enable importing .json files. */ 45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 46 | 47 | /* JavaScript Support */ 48 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 49 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 50 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 51 | 52 | /* Emit */ 53 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 54 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 56 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 67 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 68 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 69 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 70 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 71 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 72 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 73 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 74 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 75 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 76 | 77 | /* Interop Constraints */ 78 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 83 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 84 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 85 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 86 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 87 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 88 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 89 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 90 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 91 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 92 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 93 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 94 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 95 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 96 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 97 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 98 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 99 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 100 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 101 | 102 | /* Completeness */ 103 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 104 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 105 | }, 106 | "include": ["./src/**/*.ts", "./src/**/*.vue", "playwright.config.ts", "tsdown.config.ts"], 107 | "exclude": ["./dist/**/*"] 108 | } 109 | --------------------------------------------------------------------------------