├── .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 |
2 |
5 |
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 |
6 |
7 |
Currently you are viewing v3 document of vue-recaptcha which is still in development
8 |
9 |
10 |
11 |
12 |
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 |
6 |
7 |
8 | {{ response ? `Get response: ${response.slice(0, 6)}...` : 'click me' }}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/docs/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
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 |
7 |
8 |
9 |
{{ response ? 'Verified' : 'Please click the checkbox' }}
10 |
11 |
12 |
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 |
2 |
3 |
4 |
5 | Easy to use
6 | Use <Checkbox /> to render reCAPTCHA checkbox
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
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 |
19 |
20 |
21 |
22 |
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 |
15 |
16 | {{ map[state] }}{{ response ? ` ${response.slice(0, 6)}...` : '' }}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/packages/docs/components/content/ThemeButton.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
21 |
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 |
13 |
14 |
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 | [](https://circleci.com/gh/DanSnow/vue-recaptcha)
4 | [](https://www.npmjs.com/package/vue-recaptcha)
5 | [](https://www.npmjs.com/package/vue-recaptcha)
6 |
7 |
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 |
47 |
48 |
51 |
52 |
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 |
23 |
36 |
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 |
33 |
34 |
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 |
51 |
52 |
53 |
54 |
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 |
33 |
34 |
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 |
43 |
44 |
45 |
46 |
47 | ```
48 |
49 | You can read more at [here](../components/challenge-v2)
50 |
--------------------------------------------------------------------------------
/packages/docs/components/content/VeeValidateCheckbox.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
38 |
39 |
--------------------------------------------------------------------------------
/packages/vue-recaptcha/src/components/Checkbox.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
58 |
59 |
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 |
21 |
22 |
23 |
{{ response ? 'Verified' : 'Please click the checkbox' }}
24 |
25 |
26 |
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 |
17 |
18 |
19 |
Checkbox
20 |
21 |
widgetID: {{ checkboxWidgetID }}
22 |
response: {{ checkboxResponse }}
23 |
24 | {{ checkboxVerified ? 'verified' : '' }}
25 |
26 |
27 |
28 |
Invisible Challenge v2
29 |
30 |
31 |
32 |
widgetID: {{ challengeV2WidgetID }}
33 |
response: {{ challengeV2Response }}
34 |
35 | {{ challengeV2Verified ? 'verified' : '' }}
36 |
37 |
38 |
39 |
Challenge v3
40 |
41 |
42 |
43 |
response: {{ challengeV3Response }}
44 |
45 |
46 | {{ challengeV3Verified ? 'verified' : '' }}
47 |
48 |
49 |
50 |
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 |
18 |
19 |
20 |
Checkbox
21 |
22 |
widgetID: {{ checkboxWidgetID }}
23 |
response: {{ checkboxResponse }}
24 |
25 | {{ checkboxVerified ? 'verified' : '' }}
26 |
27 |
28 |
29 |
Invisible Challenge v2
30 |
31 |
32 |
33 |
widgetID: {{ challengeV2WidgetID }}
34 |
response: {{ challengeV2Response }}
35 |
36 | {{ challengeV2Verified ? 'verified' : '' }}
37 |
38 |
39 |
40 |
Challenge v3
41 |
42 |
43 |
44 |
response: {{ challengeV3Response }}
45 |
46 |
47 | {{ challengeV3Verified ? 'verified' : '' }}
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/packages/playground-vite/src/App.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
Checkbox
21 |
22 |
widgetID: {{ checkboxWidgetID }}
23 |
response: {{ checkboxResponse }}
24 |
25 | {{ checkboxVerified ? 'verified' : '' }}
26 |
27 |
28 |
29 |
Invisible Challenge v2
30 |
31 |
32 |
33 |
widgetID: {{ challengeV2WidgetID }}
34 |
response: {{ challengeV2Response }}
35 |
36 | {{ challengeV2Verified ? 'verified' : '' }}
37 |
38 |
39 |
40 |
Challenge v3
41 |
42 |
43 |
44 |
response: {{ challengeV3Response }}
45 |
46 |
47 | {{ challengeV3Verified ? 'verified' : '' }}
48 |
49 |
50 |
51 |
52 |
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 |
20 |
21 |
22 | {{ response ? `Get response: ${response.slice(0, 6)}...` : 'click me' }}
23 |
24 |
25 |
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 |
102 |
103 |
104 |
105 |
106 |
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 |
33 |
52 |
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 |
86 |
101 |
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 |
76 |
77 |
78 |
79 |
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 |
115 |
116 |
117 | ```
118 |
119 | ### Invisible reCAPTCHA
120 |
121 | ```vue
122 |
137 |
138 |
139 |
140 |
143 |
144 |
145 |
146 |
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 |
29 |
30 | {{ map[state] }}{{ response ? ` ${response.slice(0, 6)}...` : '' }}
31 |
32 |
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 |
16 |
--------------------------------------------------------------------------------
/packages/docs/public/logo-dark.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------