|
|
|
@ -1,3 +1,5 @@
@@ -1,3 +1,5 @@
|
|
|
|
|
import classNames from "classnames"; |
|
|
|
|
import { useRef } from "react"; |
|
|
|
|
import Turnstile, { BoundTurnstileObject } from "react-turnstile"; |
|
|
|
|
import { create } from "zustand"; |
|
|
|
|
import { immer } from "zustand/middleware/immer"; |
|
|
|
@ -6,19 +8,31 @@ import { reportCaptchaSolve } from "@/backend/helpers/report";
@@ -6,19 +8,31 @@ import { reportCaptchaSolve } from "@/backend/helpers/report";
|
|
|
|
|
import { conf } from "@/setup/config"; |
|
|
|
|
|
|
|
|
|
export interface TurnstileStore { |
|
|
|
|
turnstile: BoundTurnstileObject | null; |
|
|
|
|
isInWidget: boolean; |
|
|
|
|
turnstiles: { |
|
|
|
|
controls: BoundTurnstileObject; |
|
|
|
|
isInPopout: boolean; |
|
|
|
|
id: string; |
|
|
|
|
}[]; |
|
|
|
|
cbs: ((token: string | null) => void)[]; |
|
|
|
|
setTurnstile(v: BoundTurnstileObject | null): void; |
|
|
|
|
setTurnstile( |
|
|
|
|
id: string, |
|
|
|
|
v: BoundTurnstileObject | null, |
|
|
|
|
isInPopout: boolean, |
|
|
|
|
): void; |
|
|
|
|
getToken(): Promise<string>; |
|
|
|
|
processToken(token: string | null): void; |
|
|
|
|
processToken(token: string | null, widgetId: string): void; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export const useTurnstileStore = create( |
|
|
|
|
immer<TurnstileStore>((set, get) => ({ |
|
|
|
|
turnstile: null, |
|
|
|
|
isInWidget: false, |
|
|
|
|
turnstiles: [], |
|
|
|
|
cbs: [], |
|
|
|
|
processToken(token) { |
|
|
|
|
processToken(token, widgetId) { |
|
|
|
|
const cbs = get().cbs; |
|
|
|
|
const turnstile = get().turnstiles.find((v) => v.id === widgetId); |
|
|
|
|
if (turnstile?.id !== widgetId) return; |
|
|
|
|
cbs.forEach((fn) => fn(token)); |
|
|
|
|
set((s) => { |
|
|
|
|
s.cbs = []; |
|
|
|
@ -37,16 +51,26 @@ export const useTurnstileStore = create(
@@ -37,16 +51,26 @@ export const useTurnstileStore = create(
|
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
setTurnstile(v) { |
|
|
|
|
setTurnstile(id, controls, isInPopout) { |
|
|
|
|
set((s) => { |
|
|
|
|
s.turnstile = v; |
|
|
|
|
s.turnstiles = s.turnstiles.filter((v) => v.id !== id); |
|
|
|
|
if (controls) { |
|
|
|
|
s.turnstiles.push({ |
|
|
|
|
controls, |
|
|
|
|
isInPopout, |
|
|
|
|
id, |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
})), |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
export function getTurnstile() { |
|
|
|
|
return useTurnstileStore.getState().turnstile; |
|
|
|
|
const turnstiles = useTurnstileStore.getState().turnstiles; |
|
|
|
|
const inPopout = turnstiles.find((v) => v.isInPopout); |
|
|
|
|
if (inPopout) return inPopout; |
|
|
|
|
return turnstiles[0]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function isTurnstileInitialized() { |
|
|
|
@ -55,9 +79,12 @@ export function isTurnstileInitialized() {
@@ -55,9 +79,12 @@ export function isTurnstileInitialized() {
|
|
|
|
|
|
|
|
|
|
export async function getTurnstileToken() { |
|
|
|
|
const turnstile = getTurnstile(); |
|
|
|
|
turnstile?.reset(); |
|
|
|
|
turnstile?.execute(); |
|
|
|
|
try { |
|
|
|
|
// I hate turnstile
|
|
|
|
|
(window as any).turnstile.execute( |
|
|
|
|
document.querySelector(`#${turnstile.id}`), |
|
|
|
|
{}, |
|
|
|
|
); |
|
|
|
|
const token = await useTurnstileStore.getState().getToken(); |
|
|
|
|
reportCaptchaSolve(true); |
|
|
|
|
return token; |
|
|
|
@ -67,23 +94,44 @@ export async function getTurnstileToken() {
@@ -67,23 +94,44 @@ export async function getTurnstileToken() {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export function TurnstileProvider() { |
|
|
|
|
export function TurnstileProvider(props: { |
|
|
|
|
isInPopout?: boolean; |
|
|
|
|
onUpdateShow?: (show: boolean) => void; |
|
|
|
|
}) { |
|
|
|
|
const siteKey = conf().TURNSTILE_KEY; |
|
|
|
|
const idRef = useRef<string | null>(null); |
|
|
|
|
const setTurnstile = useTurnstileStore((s) => s.setTurnstile); |
|
|
|
|
const processToken = useTurnstileStore((s) => s.processToken); |
|
|
|
|
if (!siteKey) return null; |
|
|
|
|
return ( |
|
|
|
|
<Turnstile |
|
|
|
|
sitekey={siteKey} |
|
|
|
|
onLoad={(_widgetId, bound) => { |
|
|
|
|
setTurnstile(bound); |
|
|
|
|
}} |
|
|
|
|
onError={() => { |
|
|
|
|
processToken(null); |
|
|
|
|
}} |
|
|
|
|
onVerify={(token) => { |
|
|
|
|
processToken(token); |
|
|
|
|
}} |
|
|
|
|
/> |
|
|
|
|
<div |
|
|
|
|
className={classNames({ |
|
|
|
|
hidden: !props.isInPopout, |
|
|
|
|
})} |
|
|
|
|
> |
|
|
|
|
<Turnstile |
|
|
|
|
sitekey={siteKey} |
|
|
|
|
onLoad={(widgetId, bound) => { |
|
|
|
|
idRef.current = widgetId; |
|
|
|
|
setTurnstile(widgetId, bound, !!props.isInPopout); |
|
|
|
|
}} |
|
|
|
|
onError={() => { |
|
|
|
|
const id = idRef.current; |
|
|
|
|
if (!id) return; |
|
|
|
|
processToken(null, id); |
|
|
|
|
}} |
|
|
|
|
onVerify={(token) => { |
|
|
|
|
const id = idRef.current; |
|
|
|
|
if (!id) return; |
|
|
|
|
processToken(token, id); |
|
|
|
|
props.onUpdateShow?.(false); |
|
|
|
|
}} |
|
|
|
|
onBeforeInteractive={() => { |
|
|
|
|
props.onUpdateShow?.(true); |
|
|
|
|
}} |
|
|
|
|
refreshExpired="never" |
|
|
|
|
execution="render" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|