Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@
"autosize": "^6.0.1",
"marked": "^17.0.2",
"marked-highlight": "^2.2.3",
"recaptcha-v3": "^1.11.3",
"vue": "^3.5.28"
},
"devDependencies": {
Expand Down
20 changes: 2 additions & 18 deletions packages/client/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,7 @@ export default [
}),
],
treeshake: 'smallest',
external: [
'@vueuse/core',
'@waline/api',
'autosize',
'marked',
'marked-highlight',
'recaptcha-v3',
'vue',
],
external: ['@vueuse/core', '@waline/api', 'autosize', 'marked', 'marked-highlight', 'vue'],
},
Comment on lines 96 to 99
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@better-captcha/vue is now a runtime import in the slim/components builds, but it isn’t listed in external. This will bundle the captcha adapter into slim.js/component.js, which is inconsistent with how other runtime deps are treated here and may increase bundle size / duplicate dependencies. Consider adding @better-captcha/vue to external (and ensure it’s a proper dependency/peerDependency).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback


// components
Expand All @@ -116,15 +108,7 @@ export default [
sourcemap: true,
},
],
external: [
'@vueuse/core',
'@waline/api',
'autosize',
'marked',
'marked-highlight',
'recaptcha-v3',
'vue',
],
external: ['@vueuse/core', '@waline/api', 'autosize', 'marked', 'marked-highlight', 'vue'],
plugins: [
vue({
isProduction: true,
Expand Down
40 changes: 33 additions & 7 deletions packages/client/src/components/CommentBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import {
} from './Icons.js';
import ImageWall from './ImageWall.vue';
import {
createRecaptchaAdapter,
createTurnstileAdapter,
useEditor,
useReCaptcha,
useTurnstile,
useUserInfo,
useUserMeta,
} from '../composables/index.js';
Expand Down Expand Up @@ -208,8 +208,16 @@ const onImageChange = (): void => {

// oxlint-disable-next-line complexity, max-statements
const submitComment = async (): Promise<void> => {
const { serverURL, lang, login, wordLimit, requiredMeta, recaptchaV3Key, turnstileKey } =
config.value;
const {
serverURL,
lang,
login,
wordLimit,
requiredMeta,
betterCaptcha,
recaptchaV3Key,
turnstileKey,
} = config.value;

const comment: WalineCommentData = {
comment: content.value,
Expand Down Expand Up @@ -285,9 +293,27 @@ const submitComment = async (): Promise<void> => {
isSubmitting.value = true;

try {
if (recaptchaV3Key) comment.recaptchaV3 = await useReCaptcha(recaptchaV3Key).execute('social');

if (turnstileKey) comment.turnstile = await useTurnstile(turnstileKey).execute('social');
const captchaConfig =
betterCaptcha ??
(recaptchaV3Key
? { provider: 'recaptchaV3', siteKey: recaptchaV3Key }
: turnstileKey
? { provider: 'turnstile', siteKey: turnstileKey }
: null);

const captchaAdapter =
captchaConfig?.provider === 'recaptchaV3'
? createRecaptchaAdapter(captchaConfig.siteKey)
: captchaConfig?.provider === 'turnstile'
? createTurnstileAdapter(captchaConfig.siteKey)
: null;

if (captchaAdapter) {
const { provider, token } = await captchaAdapter.execute('social');

if (provider === 'recaptchaV3') comment.recaptchaV3 = token;
if (provider === 'turnstile') comment.turnstile = token;
}

const options = {
serverURL,
Expand Down
97 changes: 97 additions & 0 deletions packages/client/src/composables/captcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useScriptTag } from '@vueuse/core';

interface ReCaptchaWindow {
ready: (fn: () => void) => void;
execute: (siteKey: string, options: { action: string }) => Promise<string>;
}

interface TurnstileOptions {
sitekey: string;
action?: string;
size?: 'normal' | 'compact';
callback?: (token: string) => void;
}

interface TurnstileWindow {
ready: (fn: () => void) => void;
render: (selector: string, options?: TurnstileOptions) => void;
}

declare global {
interface Window {
grecaptcha?: ReCaptchaWindow;
turnstile?: TurnstileWindow;
}
}

export interface CaptchaResult {
provider: string;
token: string;
}

export interface CaptchaAdapter {
execute: (action: string) => Promise<CaptchaResult>;
}

const recaptchaScriptStore: Record<string, Promise<void>> = {};

const loadRecaptcha = (siteKey: string): Promise<void> =>
(recaptchaScriptStore[siteKey] ??= (async () => {
const { load } = useScriptTag(
`https://recaptcha.net/recaptcha/api.js?render=${siteKey}`,
undefined,
{ async: true },
);

await load();
})());

export const createRecaptchaAdapter = (siteKey: string): CaptchaAdapter => ({
execute: async (action: string) => {
await loadRecaptcha(siteKey);

return new Promise((resolve, reject) => {
window.grecaptcha?.ready(() => {
void window.grecaptcha
?.execute(siteKey, { action })
.then((token) => {
resolve({ provider: 'recaptchaV3', token });
})
.catch((err: unknown) => {
reject(err);
});
});

if (!window.grecaptcha)
reject(new Error('Recaptcha script is not available, please check your site key.'));
});
},
});

export const createTurnstileAdapter = (siteKey: string): CaptchaAdapter => ({
execute: async (action: string) => {
const { load } = useScriptTag(
'https://challenges.cloudflare.com/turnstile/v0/api.js',
undefined,
{ async: false },
);

await load();

return new Promise((resolve, reject) => {
window.turnstile?.ready(() => {
window.turnstile?.render('.wl-captcha-container', {
sitekey: siteKey,
action,
size: 'compact',
callback: (token) => {
resolve({ provider: 'turnstile', token });
},
});
});

if (!window.turnstile)
reject(new Error('Turnstile script is not available, please check your site key.'));
});
},
});
3 changes: 1 addition & 2 deletions packages/client/src/composables/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from './inputs.js';
export * from './like.js';
export * from './reaction.js';
export * from './recaptchaV3.js';
export * from './turnstile.js';
export * from './captcha.js';
export * from './userInfo.js';
19 changes: 0 additions & 19 deletions packages/client/src/composables/recaptchaV3.ts

This file was deleted.

51 changes: 0 additions & 51 deletions packages/client/src/composables/turnstile.ts

This file was deleted.

23 changes: 23 additions & 0 deletions packages/client/src/typings/waline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ import type {
} from './base.js';
import type { WalineLocale } from './locale.js';

export interface WalineBetterCaptchaOptions {
/**
* captcha provider name
*/
provider: 'recaptchaV3' | 'turnstile';

/**
* captcha provider site key
*/
siteKey: string;
}

export interface WalineProps {
/**
* Waline 的服务端地址
Expand Down Expand Up @@ -195,17 +207,28 @@ export interface WalineProps {
*/
noRss?: boolean;

/**
* Better Captcha 配置
*
* Better Captcha options
*/
betterCaptcha?: WalineBetterCaptchaOptions;

/**
* recaptcha v3 客户端 key
*
* recaptcha v3 client key
*
* @deprecated Please use `betterCaptcha` instead.
*/
recaptchaV3Key?: string;

/**
* turnstile 客户端 key
*
* turnstile client key
*
* @deprecated Please use `betterCaptcha` instead.
*/
turnstileKey?: string;

Expand Down
20 changes: 18 additions & 2 deletions packages/client/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ export interface WalineEmojiConfig {
export interface WalineConfig extends Required<
Omit<
WalineProps,
'emoji' | 'imageUploader' | 'highlighter' | 'texRenderer' | 'wordLimit' | 'reaction' | 'search'
| 'emoji'
| 'imageUploader'
| 'highlighter'
| 'texRenderer'
| 'wordLimit'
| 'reaction'
| 'search'
| 'betterCaptcha'
>
> {
locale: WalineLocale;
Expand All @@ -41,6 +48,7 @@ export interface WalineConfig extends Required<
texRenderer: WalineTeXRenderer | null;
search: WalineSearchOptions | null;
reaction: string[] | null;
betterCaptcha: WalineProps['betterCaptcha'] | null;
}

export const getServerURL = (serverURL: string): string => {
Expand Down Expand Up @@ -68,6 +76,7 @@ export const getConfig = ({
noCopyright = false,
noRss = false,
login = 'enable',
betterCaptcha,
recaptchaV3Key = '',
turnstileKey = '',
commentSorting = 'latest',
Expand Down Expand Up @@ -95,11 +104,18 @@ export const getConfig = ({
login,
noCopyright,
noRss,
betterCaptcha:
betterCaptcha ??
(recaptchaV3Key
? { provider: 'recaptchaV3', siteKey: recaptchaV3Key }
: turnstileKey
? { provider: 'turnstile', siteKey: turnstileKey }
Comment thread
lizheming marked this conversation as resolved.
Outdated
: null),
recaptchaV3Key,
turnstileKey,
...more,
// oxlint-disable-next-line typescript/strict-boolean-expressions, typescript/prefer-nullish-coalescing
reaction: reaction === true ? DEFAULT_REACTION : reaction || null,
reaction: reaction === true ? DEFAULT_REACTION : reaction ?? null,
imageUploader: fallback(imageUploader, defaultUploadImage),
highlighter: fallback(highlighter, defaultHighlighter),
texRenderer: fallback(texRenderer, defaultTeXRenderer),
Expand Down
4 changes: 2 additions & 2 deletions packages/client/template/init.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
pageview: true,
serverURL: import.meta.env.SERVERURL || 'https://walinejs.comment.lithub.cc',
emoji: ['//unpkg.com/@waline/emojis@1.1.0/bilibili', '//unpkg.com/@waline/emojis@1.1.0/qq'],
// recaptchaV3Key: '6Lfz4-shAAAAANgsYRR0datkzv6zLIaKrSqfHsiG',
turnstileKey: '0x4AAAAAAACTzOE6F_3MuL-L',
// betterCaptcha: { provider: 'recaptchaV3', siteKey: '6Lfz4-shAAAAANgsYRR0datkzv6zLIaKrSqfHsiG' },
betterCaptcha: { provider: 'turnstile', siteKey: '0x4AAAAAAACTzOE6F_3MuL-L' },
});
</script>
</body>
Expand Down
Loading