You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by li...@apache.org on 2024/05/06 08:49:41 UTC
(incubator-answer-plugins) 01/13: feat: add captcha plugin
This is an automated email from the ASF dual-hosted git repository.
linkinstar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-answer-plugins.git
commit 64b6be34b1108538ca0dc337d83b2c982583623c
Author: shuai <li...@sifou.com>
AuthorDate: Tue Apr 9 11:47:05 2024 +0800
feat: add captcha plugin
---
captcha-basic/.eslintrc.cjs | 19 ++
captcha-basic/Captcha.go | 23 ++
captcha-basic/Captcha.tsx | 5 +
captcha-basic/README.md | 2 +
captcha-basic/i18n/en_US.json | 12 +
captcha-basic/i18n/en_US.yaml | 14 +
captcha-basic/i18n/index.ts | 7 +
captcha-basic/i18n/translation.go | 6 +
captcha-basic/i18n/zh_CN.json | 12 +
captcha-basic/i18n/zh_CN.yaml | 9 +
captcha-basic/index.ts | 15 +
captcha-basic/interface.ts | 40 +++
captcha-basic/node_modules/.bin/acorn | 17 ++
captcha-basic/node_modules/.bin/eslint | 17 ++
captcha-basic/node_modules/.bin/tsc | 17 ++
captcha-basic/node_modules/.bin/tsserver | 17 ++
captcha-basic/node_modules/.bin/vite | 17 ++
.../node_modules/@typescript-eslint/eslint-plugin | 1 +
.../node_modules/@typescript-eslint/parser | 1 +
.../node_modules/@vitejs/plugin-react-swc | 1 +
captcha-basic/node_modules/eslint | 1 +
.../node_modules/eslint-plugin-react-hooks | 1 +
.../node_modules/eslint-plugin-react-refresh | 1 +
captcha-basic/node_modules/typescript | 1 +
captcha-basic/node_modules/vite | 1 +
captcha-basic/package.json | 35 +++
captcha-basic/tsconfig.json | 31 ++
captcha-basic/tsconfig.node.json | 10 +
captcha-basic/useCaptcha.tsx | 307 ++++++++++++++++++++
captcha-basic/vite.config.ts | 33 +++
captcha-google-v2/.eslintrc.cjs | 19 ++
captcha-google-v2/Captcha.go | 23 ++
captcha-google-v2/Captcha.tsx | 9 +
captcha-google-v2/README.md | 2 +
captcha-google-v2/common.ts | 12 +
captcha-google-v2/i18n/en_US.json | 12 +
captcha-google-v2/i18n/en_US.yaml | 14 +
captcha-google-v2/i18n/index.ts | 7 +
captcha-google-v2/i18n/translation.go | 6 +
captcha-google-v2/i18n/zh_CN.json | 12 +
captcha-google-v2/i18n/zh_CN.yaml | 9 +
captcha-google-v2/index.ts | 15 +
captcha-google-v2/interface.ts | 40 +++
captcha-google-v2/node_modules/.bin/acorn | 17 ++
captcha-google-v2/node_modules/.bin/eslint | 17 ++
captcha-google-v2/node_modules/.bin/tsc | 17 ++
captcha-google-v2/node_modules/.bin/tsserver | 17 ++
captcha-google-v2/node_modules/.bin/vite | 17 ++
.../node_modules/@typescript-eslint/eslint-plugin | 1 +
.../node_modules/@typescript-eslint/parser | 1 +
.../node_modules/@vitejs/plugin-react-swc | 1 +
captcha-google-v2/node_modules/eslint | 1 +
.../node_modules/eslint-plugin-react-hooks | 1 +
.../node_modules/eslint-plugin-react-refresh | 1 +
.../node_modules/react-google-recaptcha | 1 +
captcha-google-v2/node_modules/typescript | 1 +
captcha-google-v2/node_modules/vite | 1 +
captcha-google-v2/package.json | 38 +++
captcha-google-v2/tsconfig.json | 31 ++
captcha-google-v2/tsconfig.node.json | 10 +
captcha-google-v2/useCaptcha.tsx | 318 +++++++++++++++++++++
captcha-google-v2/vite.config.ts | 35 +++
62 files changed, 1379 insertions(+)
diff --git a/captcha-basic/.eslintrc.cjs b/captcha-basic/.eslintrc.cjs
new file mode 100644
index 0000000..c03c28c
--- /dev/null
+++ b/captcha-basic/.eslintrc.cjs
@@ -0,0 +1,19 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parser: '@typescript-eslint/parser',
+ plugins: ['react-refresh'],
+ rules: {
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ "@typescript-eslint/no-explicit-any": "off"
+ },
+}
diff --git a/captcha-basic/Captcha.go b/captcha-basic/Captcha.go
new file mode 100644
index 0000000..5ed9dfb
--- /dev/null
+++ b/captcha-basic/Captcha.go
@@ -0,0 +1,23 @@
+package chart
+
+import (
+ "github.com/apache/incubator-answer-plugins/captcha_basic/i18n"
+ "github.com/apache/incubator-answer/plugin"
+)
+
+type ChartPlugin struct {
+}
+
+func init() {
+ plugin.Register(&ChartPlugin{})
+}
+
+func (d ChartPlugin) Info() plugin.Info {
+ return plugin.Info{
+ Name: plugin.MakeTranslator(i18n.InfoName),
+ SlugName: "captcha_basic",
+ Description: plugin.MakeTranslator(i18n.InfoDescription),
+ Author: "answerdev",
+ Version: "0.0.1",
+ }
+}
diff --git a/captcha-basic/Captcha.tsx b/captcha-basic/Captcha.tsx
new file mode 100644
index 0000000..5780ced
--- /dev/null
+++ b/captcha-basic/Captcha.tsx
@@ -0,0 +1,5 @@
+const Index = () => {
+ return <div>111 {process.env.REACT_APP_API_URL }\n {process.env.REACT_APP_BASE_URL}</div>;
+};
+
+export default Index;
diff --git a/captcha-basic/README.md b/captcha-basic/README.md
new file mode 100644
index 0000000..9af7af1
--- /dev/null
+++ b/captcha-basic/README.md
@@ -0,0 +1,2 @@
+# captcha basic
+This plug-in is the default graphical verification code plug-in, used to do some human-machine verification, prevent malicious registration and malicious submission, etc.
diff --git a/captcha-basic/i18n/en_US.json b/captcha-basic/i18n/en_US.json
new file mode 100644
index 0000000..76a3b84
--- /dev/null
+++ b/captcha-basic/i18n/en_US.json
@@ -0,0 +1,12 @@
+{
+ "plugin": {
+ "captcha_basic": {
+ "title": "Captcha",
+ "placeholder": "Type the text above",
+ "msg": {
+ "empty": "Captcha cannot be empty."
+ },
+ "verify": "Verify"
+ }
+ }
+}
diff --git a/captcha-basic/i18n/en_US.yaml b/captcha-basic/i18n/en_US.yaml
new file mode 100644
index 0000000..66394e3
--- /dev/null
+++ b/captcha-basic/i18n/en_US.yaml
@@ -0,0 +1,14 @@
+plugin:
+ captcha_basic:
+ backend:
+ info:
+ name:
+ other: Captcha Basic
+ description:
+ other: Default graphic verification code
+ frontend:
+ info:
+ name:
+ other: Captcha Basic
+ description:
+ other: Default graphic verification code
diff --git a/captcha-basic/i18n/index.ts b/captcha-basic/i18n/index.ts
new file mode 100644
index 0000000..bc0b816
--- /dev/null
+++ b/captcha-basic/i18n/index.ts
@@ -0,0 +1,7 @@
+import en_US from './en_US.json';
+import zh_CN from './zh_CN.json';
+
+export default {
+ en_US,
+ zh_CN,
+};
diff --git a/captcha-basic/i18n/translation.go b/captcha-basic/i18n/translation.go
new file mode 100644
index 0000000..f6f38a3
--- /dev/null
+++ b/captcha-basic/i18n/translation.go
@@ -0,0 +1,6 @@
+package i18n
+
+const (
+ InfoName = "plugin.captcha_basic.backend.info.name"
+ InfoDescription = "plugin.captcha_basic.backend.info.description"
+)
diff --git a/captcha-basic/i18n/zh_CN.json b/captcha-basic/i18n/zh_CN.json
new file mode 100644
index 0000000..76a3b84
--- /dev/null
+++ b/captcha-basic/i18n/zh_CN.json
@@ -0,0 +1,12 @@
+{
+ "plugin": {
+ "captcha_basic": {
+ "title": "Captcha",
+ "placeholder": "Type the text above",
+ "msg": {
+ "empty": "Captcha cannot be empty."
+ },
+ "verify": "Verify"
+ }
+ }
+}
diff --git a/captcha-basic/i18n/zh_CN.yaml b/captcha-basic/i18n/zh_CN.yaml
new file mode 100644
index 0000000..bbaad87
--- /dev/null
+++ b/captcha-basic/i18n/zh_CN.yaml
@@ -0,0 +1,9 @@
+plugin:
+ captcha_basic:
+ backend:
+ info:
+ name:
+ other: 验证码
+ description:
+ other: 默认图形验证码
+
diff --git a/captcha-basic/index.ts b/captcha-basic/index.ts
new file mode 100644
index 0000000..5961351
--- /dev/null
+++ b/captcha-basic/index.ts
@@ -0,0 +1,15 @@
+import Captcha from './Captcha';
+import i18nConfig from './i18n';
+import useCaptcha from './useCaptcha';
+
+export default {
+ info: {
+ type: 'captcha',
+ slug_name: 'captcha_basic',
+ },
+ component: Captcha,
+ i18nConfig,
+ hooks: {
+ useCaptcha: useCaptcha,
+ },
+};
diff --git a/captcha-basic/interface.ts b/captcha-basic/interface.ts
new file mode 100644
index 0000000..3366902
--- /dev/null
+++ b/captcha-basic/interface.ts
@@ -0,0 +1,40 @@
+export interface FormValue<T = any> {
+ value: T;
+ isInvalid: boolean;
+ errorMsg: string;
+ [prop: string]: any;
+}
+
+export interface FormDataType {
+ [prop: string]: FormValue;
+}
+
+export interface FieldError {
+ error_field: string;
+ error_msg: string;
+}
+
+export interface ImgCodeReq {
+ captcha_id?: string;
+ captcha_code?: string;
+}
+
+export interface ImgCodeRes {
+ captcha_id: string;
+ captcha_img: string;
+ verify: boolean;
+}
+
+export type CaptchaKey =
+ | 'email'
+ | 'password'
+ | 'edit_userinfo'
+ | 'question'
+ | 'answer'
+ | 'comment'
+ | 'edit'
+ | 'invitation_answer'
+ | 'search'
+ | 'report'
+ | 'delete'
+ | 'vote';
diff --git a/captcha-basic/node_modules/.bin/acorn b/captcha-basic/node_modules/.bin/acorn
new file mode 100755
index 0000000..b42329e
--- /dev/null
+++ b/captcha-basic/node_modules/.bin/acorn
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/acorn" "$@"
+else
+ exec node "$basedir/../../../../../node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/acorn" "$@"
+fi
diff --git a/captcha-basic/node_modules/.bin/eslint b/captcha-basic/node_modules/.bin/eslint
new file mode 100755
index 0000000..628d350
--- /dev/null
+++ b/captcha-basic/node_modules/.bin/eslint
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../eslint/bin/eslint.js" "$@"
+else
+ exec node "$basedir/../eslint/bin/eslint.js" "$@"
+fi
diff --git a/captcha-basic/node_modules/.bin/tsc b/captcha-basic/node_modules/.bin/tsc
new file mode 100755
index 0000000..3591c45
--- /dev/null
+++ b/captcha-basic/node_modules/.bin/tsc
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
+else
+ exec node "$basedir/../typescript/bin/tsc" "$@"
+fi
diff --git a/captcha-basic/node_modules/.bin/tsserver b/captcha-basic/node_modules/.bin/tsserver
new file mode 100755
index 0000000..494465b
--- /dev/null
+++ b/captcha-basic/node_modules/.bin/tsserver
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
+else
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
+fi
diff --git a/captcha-basic/node_modules/.bin/vite b/captcha-basic/node_modules/.bin/vite
new file mode 100755
index 0000000..8def179
--- /dev/null
+++ b/captcha-basic/node_modules/.bin/vite
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0_@types+node@16.11.59_sass@1.54.9/node_modules/vite/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0_@types+node@16.11.59_sass@1.54.9/node_modules/vite/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0_@types+node@16.11.59_sass@1.54.9/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0_@types+node@16.11.59_sass@1.54.9/node_modules/vite/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0_@types+node@16.11.59_sass@1.54.9/node_modules/vite/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0_@types+node@16.11.59_sass@1.54.9/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
+else
+ exec node "$basedir/../vite/bin/vite.js" "$@"
+fi
diff --git a/captcha-basic/node_modules/@typescript-eslint/eslint-plugin b/captcha-basic/node_modules/@typescript-eslint/eslint-plugin
new file mode 120000
index 0000000..a5ee89e
--- /dev/null
+++ b/captcha-basic/node_modules/@typescript-eslint/eslint-plugin
@@ -0,0 +1 @@
+../../../../../node_modules/.pnpm/@typescript-eslint+eslint-plugin@6.11.0_@typescript-eslint+parser@6.11.0_eslint@8.53.0_typescript@5.3.2/node_modules/@typescript-eslint/eslint-plugin
\ No newline at end of file
diff --git a/captcha-basic/node_modules/@typescript-eslint/parser b/captcha-basic/node_modules/@typescript-eslint/parser
new file mode 120000
index 0000000..0608c9e
--- /dev/null
+++ b/captcha-basic/node_modules/@typescript-eslint/parser
@@ -0,0 +1 @@
+../../../../../node_modules/.pnpm/@typescript-eslint+parser@6.11.0_eslint@8.53.0_typescript@5.3.2/node_modules/@typescript-eslint/parser
\ No newline at end of file
diff --git a/captcha-basic/node_modules/@vitejs/plugin-react-swc b/captcha-basic/node_modules/@vitejs/plugin-react-swc
new file mode 120000
index 0000000..2d2c027
--- /dev/null
+++ b/captcha-basic/node_modules/@vitejs/plugin-react-swc
@@ -0,0 +1 @@
+../../../../../node_modules/.pnpm/@vitejs+plugin-react-swc@3.5.0_vite@4.5.0/node_modules/@vitejs/plugin-react-swc
\ No newline at end of file
diff --git a/captcha-basic/node_modules/eslint b/captcha-basic/node_modules/eslint
new file mode 120000
index 0000000..ca3f14f
--- /dev/null
+++ b/captcha-basic/node_modules/eslint
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint
\ No newline at end of file
diff --git a/captcha-basic/node_modules/eslint-plugin-react-hooks b/captcha-basic/node_modules/eslint-plugin-react-hooks
new file mode 120000
index 0000000..d6575de
--- /dev/null
+++ b/captcha-basic/node_modules/eslint-plugin-react-hooks
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/eslint-plugin-react-hooks@4.6.0_eslint@8.53.0/node_modules/eslint-plugin-react-hooks
\ No newline at end of file
diff --git a/captcha-basic/node_modules/eslint-plugin-react-refresh b/captcha-basic/node_modules/eslint-plugin-react-refresh
new file mode 120000
index 0000000..53f02e1
--- /dev/null
+++ b/captcha-basic/node_modules/eslint-plugin-react-refresh
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/eslint-plugin-react-refresh@0.4.5_eslint@8.53.0/node_modules/eslint-plugin-react-refresh
\ No newline at end of file
diff --git a/captcha-basic/node_modules/typescript b/captcha-basic/node_modules/typescript
new file mode 120000
index 0000000..ea020bc
--- /dev/null
+++ b/captcha-basic/node_modules/typescript
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/typescript@5.3.2/node_modules/typescript
\ No newline at end of file
diff --git a/captcha-basic/node_modules/vite b/captcha-basic/node_modules/vite
new file mode 120000
index 0000000..2f262d2
--- /dev/null
+++ b/captcha-basic/node_modules/vite
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/vite@4.5.0_@types+node@16.11.59_sass@1.54.9/node_modules/vite
\ No newline at end of file
diff --git a/captcha-basic/package.json b/captcha-basic/package.json
new file mode 100644
index 0000000..7574c09
--- /dev/null
+++ b/captcha-basic/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "captcha_basic",
+ "private": true,
+ "author": "Answer.dev",
+ "description": "Basic for captcha",
+ "version": "0.0.1",
+ "files": [
+ "dist",
+ "README.md"
+ ],
+ "main": "./dist/captcha_basic.umd.js",
+ "module": "./dist/captcha_basic.es.js",
+ "exports": {
+ ".": {
+ "import": "./dist/captcha_basic.es.js",
+ "require": "./dist/captcha_basic.umd.js"
+ }
+ },
+ "scripts": {
+ "dev": "vite build --mode development --watch",
+ "build": "tsc && vite build",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitejs/plugin-react-swc": "^3.3.2",
+ "eslint": "^8.45.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "typescript": "^5.0.2",
+ "vite": "^4.4.5"
+ }
+}
diff --git a/captcha-basic/tsconfig.json b/captcha-basic/tsconfig.json
new file mode 100644
index 0000000..f6c88fb
--- /dev/null
+++ b/captcha-basic/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": [
+ "ES2020",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noImplicitAny": false,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ },
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
diff --git a/captcha-basic/tsconfig.node.json b/captcha-basic/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/captcha-basic/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/captcha-basic/useCaptcha.tsx b/captcha-basic/useCaptcha.tsx
new file mode 100644
index 0000000..152d333
--- /dev/null
+++ b/captcha-basic/useCaptcha.tsx
@@ -0,0 +1,307 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useEffect, useRef, useState, useLayoutEffect } from 'react';
+import { Modal, Form, Button, InputGroup } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import ReactDOM from 'react-dom/client';
+
+import type {
+ FormValue,
+ ImgCodeRes,
+ CaptchaKey,
+ FieldError,
+ ImgCodeReq,
+} from './interface';
+
+const checkImgCode = (key: CaptchaKey) => {
+ return new Promise<ImgCodeRes>((resolve) => {
+ fetch(`/answer/api/v1/user/action/record?action=${key}`)
+ .then((resp) => {
+ return resp.json();
+ })
+ .then((data) => {
+ console.log('checkImgCode', data);
+ resolve(data.data);
+ });
+ });
+};
+
+type SubmitCallback = {
+ (): void;
+};
+
+const Index = (captchaKey: CaptchaKey) => {
+ const refRoot = useRef<ReactDOM.Root | null>(null);
+ if (refRoot.current === null) {
+ refRoot.current = ReactDOM.createRoot(document.createElement('div'));
+ }
+
+ const { t } = useTranslation('plugin', {
+ keyPrefix: 'captcha_basic',
+ });
+ const refKey = useRef<CaptchaKey>(captchaKey);
+ const refCallback = useRef<SubmitCallback>();
+ const pending = useRef(false);
+ const autoInitCaptchaData = /email/i.test(refKey.current);
+
+ const [stateShow, setStateShow] = useState(false);
+ const [captcha, setCaptcha] = useState<ImgCodeRes>({
+ captcha_id: '',
+ captcha_img: '',
+ verify: false,
+ });
+ const [imgCode, setImgCode] = useState<FormValue>({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ const refCaptcha = useRef(captcha);
+ const refImgCode = useRef(imgCode);
+
+ const fetchCaptchaData = () => {
+ pending.current = true;
+ checkImgCode(refKey.current)
+ .then((resp) => {
+ setCaptcha(resp);
+ })
+ .finally(() => {
+ pending.current = false;
+ });
+ };
+
+ const resetCapture = () => {
+ setCaptcha({
+ captcha_id: '',
+ captcha_img: '',
+ verify: false,
+ });
+ };
+
+ const resetImgCode = () => {
+ setImgCode({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ };
+ const resetCallback = () => {
+ refCallback.current = undefined;
+ };
+
+ const show = () => {
+ if (!stateShow) {
+ setStateShow(true);
+ }
+ };
+ /**
+ * There are some cases where the React scheduler cancels the execution of some functions,
+ * which prevents them from closing properly:
+ * for example, if the parent component uninstalls the child component directly,
+ * and the `captchaModal.close()` call is inside the child component.
+ * In this case, call `await captchaModal.close()` and wait for the close action to complete.
+ */
+ const close = () => {
+ setStateShow(false);
+ resetCapture();
+ resetImgCode();
+ resetCallback();
+
+ const p = new Promise<void>((resolve) => {
+ setTimeout(resolve, 50);
+ });
+ return p;
+ };
+
+ const handleCaptchaError = (fel: FieldError[] = []) => {
+ console.log('handleCaptchaError', fel);
+ const captchaErr = fel.find((o) => {
+ return o.error_field === 'captcha_code';
+ });
+
+ const ri = refImgCode.current;
+ if (captchaErr) {
+ /**
+ * `imgCode.value` No value but a validation error is received,
+ * indicating that it is the first time the interface has returned a CAPTCHA error,
+ * triggering the CAPTCHA logic. There is no need to display the error message at this point.
+ */
+ if (ri.value) {
+ setImgCode({
+ ...ri,
+ isInvalid: true,
+ errorMsg: captchaErr.error_msg,
+ });
+ }
+ fetchCaptchaData();
+ show();
+ } else {
+ close();
+ }
+ // Assist business logic in filtering CAPTCHA error messages when necessary
+ return captchaErr;
+ };
+
+ const handleChange = (evt) => {
+ evt.preventDefault();
+ setImgCode({
+ value: evt.target.value || '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ };
+
+ const getCaptcha = () => {
+ const rc = refCaptcha.current;
+ const ri = refImgCode.current;
+ const r = {
+ verify: !!rc?.verify,
+ captcha_id: rc?.captcha_id,
+ captcha_code: ri.value,
+ };
+
+ return r;
+ };
+
+ const resolveCaptchaReq = (req: ImgCodeReq) => {
+ const r = getCaptcha();
+ if (r.verify) {
+ req.captcha_code = r.captcha_code;
+ req.captcha_id = r.captcha_id;
+ }
+ };
+
+ const handleSubmit = (evt) => {
+ evt.preventDefault();
+ if (!imgCode.value) {
+ return;
+ }
+
+ if (refCallback.current) {
+ refCallback.current();
+ }
+ };
+
+ useEffect(() => {
+ if (autoInitCaptchaData) {
+ fetchCaptchaData();
+ }
+ }, []);
+
+ useLayoutEffect(() => {
+ refImgCode.current = imgCode;
+ refCaptcha.current = captcha;
+ }, [captcha, imgCode]);
+
+ useEffect(() => {
+ refRoot.current?.render(
+ <Modal
+ size="sm"
+ title="Captcha"
+ show={stateShow}
+ onHide={() => close()}
+ centered>
+ <Modal.Header closeButton>
+ <Modal.Title as="h5">{t('title')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form noValidate onSubmit={handleSubmit}>
+ <Form.Group controlId="code" className="mb-3">
+ <div className="mb-3 p-2 d-flex align-items-center justify-content-center bg-light rounded-2">
+ <img
+ src={captcha?.captcha_img}
+ alt="captcha img"
+ width="auto"
+ height="60px"
+ />
+ </div>
+ <InputGroup>
+ <Form.Control
+ type="text"
+ autoComplete="off"
+ placeholder={t('placeholder')}
+ isInvalid={imgCode?.isInvalid}
+ onChange={handleChange}
+ value={imgCode.value}
+ />
+ <Button
+ onClick={fetchCaptchaData}
+ variant="outline-secondary"
+ title={t('refresh', { keyPrefix: 'btns' })}
+ style={{
+ borderTopRightRadius: '0.375rem',
+ borderBottomRightRadius: '0.375rem',
+ }}>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="16"
+ height="16"
+ fill="currentColor"
+ className="bi bi-arrow-repeat"
+ viewBox="0 0 16 16">
+ <path d="M11.534 7h3.932a.25.25 0 0 1 .192.41l-1.966 2.36a.25.25 0 0 1-.384 0l-1.966-2.36a.25.25 0 0 1 .192-.41m-11 2h3.932a.25.25 0 0 0 .192-.41L2.692 6.23a.25.25 0 0 0-.384 0L.342 8.59A.25.25 0 0 0 .534 9" />
+ <path
+ fillRule="evenodd"
+ d="M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5 5 0 0 0 8 3M3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9z"
+ />
+ </svg>
+ </Button>
+
+ <Form.Control.Feedback type="invalid">
+ {imgCode?.errorMsg}
+ </Form.Control.Feedback>
+ </InputGroup>
+ </Form.Group>
+
+ <div className="d-grid">
+ <Button type="submit" disabled={!imgCode.value}>
+ {t('verify')}
+ </Button>
+ </div>
+ </Form>
+ </Modal.Body>
+ </Modal>,
+ );
+ });
+
+ const r = {
+ close,
+ show,
+ check: (submitFunc: SubmitCallback) => {
+ if (pending.current) {
+ return false;
+ }
+ refCallback.current = submitFunc;
+ if (captcha?.verify) {
+ show();
+ return false;
+ }
+ return submitFunc();
+ },
+ getCaptcha,
+ resolveCaptchaReq,
+ fetchCaptchaData,
+ handleCaptchaError,
+ };
+
+ return r;
+};
+
+export default Index;
diff --git a/captcha-basic/vite.config.ts b/captcha-basic/vite.config.ts
new file mode 100644
index 0000000..d3c9777
--- /dev/null
+++ b/captcha-basic/vite.config.ts
@@ -0,0 +1,33 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react-swc';
+import packageJson from './package.json';
+
+// https://vitejs.dev/config/
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ lib: {
+ entry: 'index.ts',
+ name: packageJson.name,
+ fileName: (format) => `${packageJson.name}.${format}.js`,
+ },
+ rollupOptions: {
+ external: [
+ 'react',
+ 'react-dom',
+ 'react-i18next',
+ 'react-bootstrap',
+ ],
+ output: {
+ globals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-i18next': 'reactI18next',
+ 'react-bootstrap': 'reactBootstrap',
+ },
+ },
+ },
+ },
+});
diff --git a/captcha-google-v2/.eslintrc.cjs b/captcha-google-v2/.eslintrc.cjs
new file mode 100644
index 0000000..c03c28c
--- /dev/null
+++ b/captcha-google-v2/.eslintrc.cjs
@@ -0,0 +1,19 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parser: '@typescript-eslint/parser',
+ plugins: ['react-refresh'],
+ rules: {
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ "@typescript-eslint/no-explicit-any": "off"
+ },
+}
diff --git a/captcha-google-v2/Captcha.go b/captcha-google-v2/Captcha.go
new file mode 100644
index 0000000..f9e144a
--- /dev/null
+++ b/captcha-google-v2/Captcha.go
@@ -0,0 +1,23 @@
+package chart
+
+import (
+ "github.com/apache/incubator-answer-plugins/captcha_google_v2/i18n"
+ "github.com/apache/incubator-answer/plugin"
+)
+
+type ChartPlugin struct {
+}
+
+func init() {
+ plugin.Register(&ChartPlugin{})
+}
+
+func (d ChartPlugin) Info() plugin.Info {
+ return plugin.Info{
+ Name: plugin.MakeTranslator(i18n.InfoName),
+ SlugName: "captcha_google_v2",
+ Description: plugin.MakeTranslator(i18n.InfoDescription),
+ Author: "answerdev",
+ Version: "0.0.1",
+ }
+}
diff --git a/captcha-google-v2/Captcha.tsx b/captcha-google-v2/Captcha.tsx
new file mode 100644
index 0000000..9d626e3
--- /dev/null
+++ b/captcha-google-v2/Captcha.tsx
@@ -0,0 +1,9 @@
+const Index = () => {
+ return (
+ <div>
+ <div>gogle v2</div>
+ </div>
+ );
+};
+
+export default Index;
diff --git a/captcha-google-v2/README.md b/captcha-google-v2/README.md
new file mode 100644
index 0000000..fddceac
--- /dev/null
+++ b/captcha-google-v2/README.md
@@ -0,0 +1,2 @@
+# captcha google v2
+This plug-in is the default graphical verification code plug-in, used to do some human-machine verification, prevent malicious registration and malicious submission, etc.
diff --git a/captcha-google-v2/common.ts b/captcha-google-v2/common.ts
new file mode 100644
index 0000000..228bc94
--- /dev/null
+++ b/captcha-google-v2/common.ts
@@ -0,0 +1,12 @@
+/**
+ * @description: System language conversion bit google reacptcah corresponding language format
+ * @link: https://developers.google.com/recaptcha/docs/language
+ */
+export const languageKeys = {
+ af_ZA: 'af',
+ ar_SA: 'ar',
+ az_AZ: 'az',
+ en_US: 'en',
+ zh_CN: 'zh-CN',
+ zh_TW: 'zh-TW',
+};
diff --git a/captcha-google-v2/i18n/en_US.json b/captcha-google-v2/i18n/en_US.json
new file mode 100644
index 0000000..7b8f588
--- /dev/null
+++ b/captcha-google-v2/i18n/en_US.json
@@ -0,0 +1,12 @@
+{
+ "plugin": {
+ "captcha_google_v2": {
+ "title": "Captcha",
+ "placeholder": "Type the text above",
+ "msg": {
+ "empty": "Captcha cannot be empty."
+ },
+ "verify": "Verify"
+ }
+ }
+}
diff --git a/captcha-google-v2/i18n/en_US.yaml b/captcha-google-v2/i18n/en_US.yaml
new file mode 100644
index 0000000..1e00fb3
--- /dev/null
+++ b/captcha-google-v2/i18n/en_US.yaml
@@ -0,0 +1,14 @@
+plugin:
+ captcha_google_v2:
+ backend:
+ info:
+ name:
+ other: Captcha Basic
+ description:
+ other: Default graphic verification code
+ frontend:
+ info:
+ name:
+ other: Captcha Basic
+ description:
+ other: Default graphic verification code
diff --git a/captcha-google-v2/i18n/index.ts b/captcha-google-v2/i18n/index.ts
new file mode 100644
index 0000000..bc0b816
--- /dev/null
+++ b/captcha-google-v2/i18n/index.ts
@@ -0,0 +1,7 @@
+import en_US from './en_US.json';
+import zh_CN from './zh_CN.json';
+
+export default {
+ en_US,
+ zh_CN,
+};
diff --git a/captcha-google-v2/i18n/translation.go b/captcha-google-v2/i18n/translation.go
new file mode 100644
index 0000000..9ccf6fa
--- /dev/null
+++ b/captcha-google-v2/i18n/translation.go
@@ -0,0 +1,6 @@
+package i18n
+
+const (
+ InfoName = "plugin.captcha_google_v2.backend.info.name"
+ InfoDescription = "plugin.captcha_google_v2.backend.info.description"
+)
diff --git a/captcha-google-v2/i18n/zh_CN.json b/captcha-google-v2/i18n/zh_CN.json
new file mode 100644
index 0000000..7b8f588
--- /dev/null
+++ b/captcha-google-v2/i18n/zh_CN.json
@@ -0,0 +1,12 @@
+{
+ "plugin": {
+ "captcha_google_v2": {
+ "title": "Captcha",
+ "placeholder": "Type the text above",
+ "msg": {
+ "empty": "Captcha cannot be empty."
+ },
+ "verify": "Verify"
+ }
+ }
+}
diff --git a/captcha-google-v2/i18n/zh_CN.yaml b/captcha-google-v2/i18n/zh_CN.yaml
new file mode 100644
index 0000000..f0e5f25
--- /dev/null
+++ b/captcha-google-v2/i18n/zh_CN.yaml
@@ -0,0 +1,9 @@
+plugin:
+ captcha_google_v2:
+ backend:
+ info:
+ name:
+ other: 验证码
+ description:
+ other: 默认图形验证码
+
diff --git a/captcha-google-v2/index.ts b/captcha-google-v2/index.ts
new file mode 100644
index 0000000..e7c6fe1
--- /dev/null
+++ b/captcha-google-v2/index.ts
@@ -0,0 +1,15 @@
+import Captcha from './Captcha';
+import i18nConfig from './i18n';
+import useCaptcha from './useCaptcha';
+
+export default {
+ info: {
+ type: 'captcha',
+ slug_name: 'captcha_google_v2',
+ },
+ component: Captcha,
+ i18nConfig,
+ hooks: {
+ useCaptcha: useCaptcha,
+ },
+};
diff --git a/captcha-google-v2/interface.ts b/captcha-google-v2/interface.ts
new file mode 100644
index 0000000..3366902
--- /dev/null
+++ b/captcha-google-v2/interface.ts
@@ -0,0 +1,40 @@
+export interface FormValue<T = any> {
+ value: T;
+ isInvalid: boolean;
+ errorMsg: string;
+ [prop: string]: any;
+}
+
+export interface FormDataType {
+ [prop: string]: FormValue;
+}
+
+export interface FieldError {
+ error_field: string;
+ error_msg: string;
+}
+
+export interface ImgCodeReq {
+ captcha_id?: string;
+ captcha_code?: string;
+}
+
+export interface ImgCodeRes {
+ captcha_id: string;
+ captcha_img: string;
+ verify: boolean;
+}
+
+export type CaptchaKey =
+ | 'email'
+ | 'password'
+ | 'edit_userinfo'
+ | 'question'
+ | 'answer'
+ | 'comment'
+ | 'edit'
+ | 'invitation_answer'
+ | 'search'
+ | 'report'
+ | 'delete'
+ | 'vote';
diff --git a/captcha-google-v2/node_modules/.bin/acorn b/captcha-google-v2/node_modules/.bin/acorn
new file mode 100755
index 0000000..b42329e
--- /dev/null
+++ b/captcha-google-v2/node_modules/.bin/acorn
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/acorn@8.11.3/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/acorn" "$@"
+else
+ exec node "$basedir/../../../../../node_modules/.pnpm/acorn@8.11.3/node_modules/acorn/bin/acorn" "$@"
+fi
diff --git a/captcha-google-v2/node_modules/.bin/eslint b/captcha-google-v2/node_modules/.bin/eslint
new file mode 100755
index 0000000..bd67541
--- /dev/null
+++ b/captcha-google-v2/node_modules/.bin/eslint
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/eslint@8.53.0/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/bin/eslint.js" "$@"
+else
+ exec node "$basedir/../../../../../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint/bin/eslint.js" "$@"
+fi
diff --git a/captcha-google-v2/node_modules/.bin/tsc b/captcha-google-v2/node_modules/.bin/tsc
new file mode 100755
index 0000000..60f3a2b
--- /dev/null
+++ b/captcha-google-v2/node_modules/.bin/tsc
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/tsc" "$@"
+else
+ exec node "$basedir/../../../../../node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/tsc" "$@"
+fi
diff --git a/captcha-google-v2/node_modules/.bin/tsserver b/captcha-google-v2/node_modules/.bin/tsserver
new file mode 100755
index 0000000..49af353
--- /dev/null
+++ b/captcha-google-v2/node_modules/.bin/tsserver
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/typescript@5.3.2/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/tsserver" "$@"
+else
+ exec node "$basedir/../../../../../node_modules/.pnpm/typescript@5.3.2/node_modules/typescript/bin/tsserver" "$@"
+fi
diff --git a/captcha-google-v2/node_modules/.bin/vite b/captcha-google-v2/node_modules/.bin/vite
new file mode 100755
index 0000000..05fe5ab
--- /dev/null
+++ b/captcha-google-v2/node_modules/.bin/vite
@@ -0,0 +1,17 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -z "$NODE_PATH" ]; then
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0/node_modules/vite/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0/node_modules/vite/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules"
+else
+ export NODE_PATH="/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0/node_modules/vite/bin/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0/node_modules/vite/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/vite@4.5.0/node_modules:/Users/shuai/answer/answer/ui/node_modules/.pnpm/node_modules:$NODE_PATH"
+fi
+if [ -x "$basedir/node" ]; then
+ exec "$basedir/node" "$basedir/../../../../../node_modules/.pnpm/vite@4.5.0/node_modules/vite/bin/vite.js" "$@"
+else
+ exec node "$basedir/../../../../../node_modules/.pnpm/vite@4.5.0/node_modules/vite/bin/vite.js" "$@"
+fi
diff --git a/captcha-google-v2/node_modules/@typescript-eslint/eslint-plugin b/captcha-google-v2/node_modules/@typescript-eslint/eslint-plugin
new file mode 120000
index 0000000..a5ee89e
--- /dev/null
+++ b/captcha-google-v2/node_modules/@typescript-eslint/eslint-plugin
@@ -0,0 +1 @@
+../../../../../node_modules/.pnpm/@typescript-eslint+eslint-plugin@6.11.0_@typescript-eslint+parser@6.11.0_eslint@8.53.0_typescript@5.3.2/node_modules/@typescript-eslint/eslint-plugin
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/@typescript-eslint/parser b/captcha-google-v2/node_modules/@typescript-eslint/parser
new file mode 120000
index 0000000..0608c9e
--- /dev/null
+++ b/captcha-google-v2/node_modules/@typescript-eslint/parser
@@ -0,0 +1 @@
+../../../../../node_modules/.pnpm/@typescript-eslint+parser@6.11.0_eslint@8.53.0_typescript@5.3.2/node_modules/@typescript-eslint/parser
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/@vitejs/plugin-react-swc b/captcha-google-v2/node_modules/@vitejs/plugin-react-swc
new file mode 120000
index 0000000..2d2c027
--- /dev/null
+++ b/captcha-google-v2/node_modules/@vitejs/plugin-react-swc
@@ -0,0 +1 @@
+../../../../../node_modules/.pnpm/@vitejs+plugin-react-swc@3.5.0_vite@4.5.0/node_modules/@vitejs/plugin-react-swc
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/eslint b/captcha-google-v2/node_modules/eslint
new file mode 120000
index 0000000..ca3f14f
--- /dev/null
+++ b/captcha-google-v2/node_modules/eslint
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/eslint@8.53.0/node_modules/eslint
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/eslint-plugin-react-hooks b/captcha-google-v2/node_modules/eslint-plugin-react-hooks
new file mode 120000
index 0000000..d6575de
--- /dev/null
+++ b/captcha-google-v2/node_modules/eslint-plugin-react-hooks
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/eslint-plugin-react-hooks@4.6.0_eslint@8.53.0/node_modules/eslint-plugin-react-hooks
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/eslint-plugin-react-refresh b/captcha-google-v2/node_modules/eslint-plugin-react-refresh
new file mode 120000
index 0000000..53f02e1
--- /dev/null
+++ b/captcha-google-v2/node_modules/eslint-plugin-react-refresh
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/eslint-plugin-react-refresh@0.4.5_eslint@8.53.0/node_modules/eslint-plugin-react-refresh
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/react-google-recaptcha b/captcha-google-v2/node_modules/react-google-recaptcha
new file mode 120000
index 0000000..f6eec60
--- /dev/null
+++ b/captcha-google-v2/node_modules/react-google-recaptcha
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/react-google-recaptcha@3.1.0_react@18.2.0/node_modules/react-google-recaptcha
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/typescript b/captcha-google-v2/node_modules/typescript
new file mode 120000
index 0000000..ea020bc
--- /dev/null
+++ b/captcha-google-v2/node_modules/typescript
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/typescript@5.3.2/node_modules/typescript
\ No newline at end of file
diff --git a/captcha-google-v2/node_modules/vite b/captcha-google-v2/node_modules/vite
new file mode 120000
index 0000000..0d47d66
--- /dev/null
+++ b/captcha-google-v2/node_modules/vite
@@ -0,0 +1 @@
+../../../../node_modules/.pnpm/vite@4.5.0/node_modules/vite
\ No newline at end of file
diff --git a/captcha-google-v2/package.json b/captcha-google-v2/package.json
new file mode 100644
index 0000000..28a2882
--- /dev/null
+++ b/captcha-google-v2/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "captcha_google_v2",
+ "private": true,
+ "author": "Answer.dev",
+ "description": "google reCaptcha v2",
+ "version": "0.0.1",
+ "files": [
+ "dist",
+ "README.md"
+ ],
+ "main": "./dist/captcha_google_v2.umd.js",
+ "module": "./dist/captcha_google_v2.es.js",
+ "exports": {
+ ".": {
+ "import": "./dist/captcha_google_v2.es.js",
+ "require": "./dist/captcha_google_v2.umd.js"
+ }
+ },
+ "scripts": {
+ "dev": "vite build --mode development --watch",
+ "build": "tsc && vite build",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitejs/plugin-react-swc": "^3.3.2",
+ "eslint": "^8.45.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "typescript": "^5.0.2",
+ "vite": "^4.4.5"
+ },
+ "dependencies": {
+ "react-google-recaptcha": "^3.1.0"
+ }
+}
diff --git a/captcha-google-v2/tsconfig.json b/captcha-google-v2/tsconfig.json
new file mode 100644
index 0000000..f6c88fb
--- /dev/null
+++ b/captcha-google-v2/tsconfig.json
@@ -0,0 +1,31 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": [
+ "ES2020",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noImplicitAny": false,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ },
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
diff --git a/captcha-google-v2/tsconfig.node.json b/captcha-google-v2/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/captcha-google-v2/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/captcha-google-v2/useCaptcha.tsx b/captcha-google-v2/useCaptcha.tsx
new file mode 100644
index 0000000..d188c9a
--- /dev/null
+++ b/captcha-google-v2/useCaptcha.tsx
@@ -0,0 +1,318 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useEffect, useRef, useState, useLayoutEffect } from 'react';
+import { Modal, Form, Button, InputGroup, Spinner } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import ReCAPTCHA from "react-google-recaptcha";
+
+import ReactDOM from 'react-dom/client';
+
+import { languageKeys } from './common';
+import type {
+ FormValue,
+ ImgCodeRes,
+ CaptchaKey,
+ FieldError,
+ ImgCodeReq,
+} from './interface';
+
+const checkImgCode = (key: CaptchaKey) => {
+ return new Promise<ImgCodeRes>((resolve) => {
+ fetch(`/answer/api/v1/user/action/record?action=${key}`)
+ .then((resp) => {
+ return resp.json();
+ })
+ .then((data) => {
+ console.log('checkImgCode', data);
+ resolve(data.data);
+ });
+ });
+};
+
+type SubmitCallback = {
+ (): void;
+};
+
+const Index = (captchaKey: CaptchaKey) => {
+ const refRoot = useRef<ReactDOM.Root | null>(null);
+
+ if (refRoot.current === null) {
+ refRoot.current = ReactDOM.createRoot(document.createElement('div'));
+ }
+
+ const { t, i18n } = useTranslation('plugin', {
+ keyPrefix: 'captcha_basic',
+ });
+
+ const refKey = useRef<CaptchaKey>(captchaKey);
+ const refCallback = useRef<SubmitCallback>();
+ const pending = useRef(false);
+ const autoInitCaptchaData = /email/i.test(refKey.current);
+
+ const [isLoading, setIsLoading] = useState(true);
+ const [stateShow, setStateShow] = useState(false);
+ const [googleKey, setGoogleKey] = useState('');
+ const [captcha, setCaptcha] = useState<ImgCodeRes>({
+ captcha_id: '',
+ captcha_img: '',
+ verify: false,
+ });
+ const [imgCode, setImgCode] = useState<FormValue>({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ const refCaptcha = useRef(captcha);
+ const refImgCode = useRef(imgCode);
+
+ const getCaptchaKey = () => {
+ fetch(`/answer/api/v1/captcha/config`)
+ .then((resp) => {
+ return resp.json();
+ })
+ .then((data) => {
+ setGoogleKey(data?.data?.config.key);
+ })
+ }
+
+ const fetchCaptchaData = () => {
+ pending.current = true;
+ checkImgCode(refKey.current)
+ .then((resp) => {
+ setCaptcha(resp);
+ })
+ .finally(() => {
+ pending.current = false;
+ });
+ };
+
+ const resetCapture = () => {
+ setCaptcha({
+ captcha_id: '',
+ captcha_img: '',
+ verify: false,
+ });
+ };
+
+ const resetImgCode = () => {
+ setImgCode({
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ };
+ const resetCallback = () => {
+ refCallback.current = undefined;
+ };
+
+ const show = () => {
+ if (!stateShow) {
+ setStateShow(true);
+ }
+ };
+ /**
+ * There are some cases where the React scheduler cancels the execution of some functions,
+ * which prevents them from closing properly:
+ * for example, if the parent component uninstalls the child component directly,
+ * and the `captchaModal.close()` call is inside the child component.
+ * In this case, call `await captchaModal.close()` and wait for the close action to complete.
+ */
+ const close = () => {
+ setStateShow(false);
+ resetCapture();
+ resetImgCode();
+ resetCallback();
+
+ const p = new Promise<void>((resolve) => {
+ setTimeout(resolve, 50);
+ });
+ return p;
+ };
+
+ const handleCaptchaError = (fel: FieldError[] = []) => {
+ console.log('handleCaptchaError', fel);
+ const captchaErr = fel.find((o) => {
+ return o.error_field === 'captcha_code';
+ });
+
+ const ri = refImgCode.current;
+ if (captchaErr) {
+ /**
+ * `imgCode.value` No value but a validation error is received,
+ * indicating that it is the first time the interface has returned a CAPTCHA error,
+ * triggering the CAPTCHA logic. There is no need to display the error message at this point.
+ */
+ if (ri.value) {
+ setImgCode({
+ ...ri,
+ isInvalid: true,
+ errorMsg: captchaErr.error_msg,
+ });
+ }
+ fetchCaptchaData();
+ show();
+ } else {
+ close();
+ }
+ // Assist business logic in filtering CAPTCHA error messages when necessary
+ return captchaErr;
+ };
+
+ const handleChange = (token) => {
+ setImgCode({
+ value: token || '',
+ isInvalid: false,
+ errorMsg: '',
+ });
+ };
+
+ const getCaptcha = () => {
+ const rc = refCaptcha.current;
+ const ri = refImgCode.current;
+ const r = {
+ verify: !!rc?.verify,
+ captcha_id: rc?.captcha_id,
+ captcha_code: ri.value,
+ };
+
+ return r;
+ };
+
+ const resolveCaptchaReq = (req: ImgCodeReq) => {
+ const r = getCaptcha();
+ if (r.verify) {
+ req.captcha_code = r.captcha_code;
+ req.captcha_id = r.captcha_id;
+ }
+ };
+
+ const handleSubmit = (evt) => {
+ evt.preventDefault();
+ if (!imgCode.value) {
+ return;
+ }
+
+ if (refCallback.current) {
+ refCallback.current();
+ }
+ };
+
+ useEffect(() => {
+ if (autoInitCaptchaData) {
+ fetchCaptchaData();
+ }
+ }, []);
+
+ useEffect(() => {
+ if (stateShow) {
+ getCaptchaKey();
+ setTimeout(() => {
+ setIsLoading(false);
+ }, 800)
+ } else {
+ setTimeout(() => {
+ setIsLoading(true);
+ }, 100)
+ }
+ }, [stateShow]);
+
+ useLayoutEffect(() => {
+ refImgCode.current = imgCode;
+ refCaptcha.current = captcha;
+ }, [captcha, imgCode]);
+
+ useEffect(() => {
+ refRoot.current?.render(
+ <Modal
+ style={{ width: '336px', margin: '0 auto', right: 0 }}
+ title="Captcha"
+ show={stateShow}
+ onHide={() => close()}
+ centered>
+ <Modal.Header closeButton>
+ <Modal.Title as="h5">{t('title')}</Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Form noValidate onSubmit={handleSubmit}>
+ <Form.Group controlId="code" className="mb-3">
+ <InputGroup>
+ {isLoading && !captchaKey && <div style={{ height: '78px' }} />}
+ <div className={isLoading ? 'w-100 text-center d-block' : 'w-100 text-center d-none'} style={{ position: 'absolute', top: 0, left: 0, height: '78px', lineHeight: '78px' }}>
+ <Spinner animation="border" variant="secondary" />
+ </div>
+ {googleKey && (
+ <ReCAPTCHA
+ sitekey={googleKey}
+ theme="light"
+ size="normal"
+ className={isLoading ? 'invisible' : 'visible'}
+ hl={languageKeys[i18n.language] || 'en'}
+ onChange={(token) =>handleChange(token)}
+ onErrored={() => resetImgCode()}
+ onExpired={() => resetImgCode()}
+ />
+ )}
+ <Form.Control
+ type="text"
+ autoComplete="off"
+ className="d-none"
+ isInvalid={imgCode?.isInvalid}
+ />
+ <Form.Control.Feedback type="invalid">
+ {imgCode?.errorMsg}
+ </Form.Control.Feedback>
+ </InputGroup>
+ </Form.Group>
+
+ <div className="d-grid">
+ <Button type="submit" disabled={!imgCode.value}>
+ {t('verify')}
+ </Button>
+ </div>
+ </Form>
+ </Modal.Body>
+ </Modal>,
+ );
+ });
+
+ const r = {
+ close,
+ show,
+ check: (submitFunc: SubmitCallback) => {
+ if (pending.current) {
+ return false;
+ }
+ refCallback.current = submitFunc;
+ if (captcha?.verify) {
+ show();
+ return false;
+ }
+ return submitFunc();
+ },
+ getCaptcha,
+ resolveCaptchaReq,
+ fetchCaptchaData,
+ handleCaptchaError,
+ };
+
+ return r;
+};
+
+export default Index;
diff --git a/captcha-google-v2/vite.config.ts b/captcha-google-v2/vite.config.ts
new file mode 100644
index 0000000..f96ea57
--- /dev/null
+++ b/captcha-google-v2/vite.config.ts
@@ -0,0 +1,35 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react-swc';
+import packageJson from './package.json';
+
+// https://vitejs.dev/config/
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ build: {
+ lib: {
+ entry: 'index.ts',
+ name: packageJson.name,
+ fileName: (format) => `${packageJson.name}.${format}.js`,
+ },
+ rollupOptions: {
+ external: [
+ 'react',
+ 'react-dom',
+ 'react-i18next',
+ 'react-bootstrap',
+ 'react-google-recaptcha',
+ ],
+ output: {
+ globals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-i18next': 'reactI18next',
+ 'react-bootstrap': 'reactBootstrap',
+ 'react-google-recaptcha': 'reactGoogleRecaptcha'
+ },
+ },
+ },
+ },
+});