@@ -36,6 +36,10 @@ interface RenderOptions {
36
36
* @param errorCode string
37
37
*/
38
38
'error-callback' ?: ( errorCode : string ) => void ;
39
+ /**
40
+ * A JavaScript callback invoked when a given client/browser is not supported by the widget.
41
+ */
42
+ 'unsupported-callback' ?: ( ) => boolean ;
39
43
/**
40
44
* Appearance controls when the widget is visible.
41
45
* It can be always (default), execute, or interaction-only.
@@ -80,32 +84,46 @@ export async function loadCaptcha(url: string) {
80
84
return window . turnstile ;
81
85
}
82
86
87
+ /*
88
+ * How this function works:
89
+ * The widgetType is either 'invisible' or 'smart'.
90
+ * - If the widgetType is 'invisible', the captcha widget is rendered in a hidden div at the bottom of the body.
91
+ * - If the widgetType is 'smart', the captcha widget is rendered in a div with the id 'clerk-captcha'. If the div does
92
+ * not exist, the invisibleSiteKey is used as a fallback and the widget is rendered in a hidden div at the bottom of the body.
93
+ */
83
94
export const getCaptchaToken = async ( captchaOptions : {
84
95
siteKey : string ;
85
96
scriptUrl : string ;
86
97
widgetType : CaptchaWidgetType ;
98
+ invisibleSiteKey : string ;
87
99
} ) => {
88
- const { siteKey : sitekey , scriptUrl, widgetType } = captchaOptions ;
100
+ const { siteKey, scriptUrl, widgetType, invisibleSiteKey } = captchaOptions ;
89
101
let captchaToken = '' ,
90
102
id = '' ;
91
- const invisibleWidget = ! widgetType || widgetType === 'invisible' ;
103
+ let invisibleWidget = ! widgetType || widgetType === 'invisible' ;
104
+ let turnstileSiteKey = siteKey ;
92
105
93
106
let widgetDiv : HTMLElement | null = null ;
94
107
95
- if ( invisibleWidget ) {
108
+ const createInvisibleDOMElement = ( ) => {
96
109
const div = document . createElement ( 'div' ) ;
97
110
div . classList . add ( CAPTCHA_INVISIBLE_CLASSNAME ) ;
98
111
document . body . appendChild ( div ) ;
99
- widgetDiv = div ;
112
+ return div ;
113
+ } ;
114
+
115
+ if ( invisibleWidget ) {
116
+ widgetDiv = createInvisibleDOMElement ( ) ;
100
117
} else {
101
118
const visibleDiv = document . getElementById ( CAPTCHA_ELEMENT_ID ) ;
102
119
if ( visibleDiv ) {
103
120
visibleDiv . style . display = 'block' ;
104
121
widgetDiv = visibleDiv ;
105
122
} else {
106
- throw {
107
- captchaError : 'Element to render the captcha not found' ,
108
- } ;
123
+ console . error ( 'Captcha DOM element not found. Using invisible captcha widget.' ) ;
124
+ widgetDiv = createInvisibleDOMElement ( ) ;
125
+ invisibleWidget = true ;
126
+ turnstileSiteKey = invisibleSiteKey ;
109
127
}
110
128
}
111
129
@@ -117,8 +135,8 @@ export const getCaptchaToken = async (captchaOptions: {
117
135
return new Promise ( ( resolve , reject ) => {
118
136
try {
119
137
const id = captcha . render ( invisibleWidget ? `.${ CAPTCHA_INVISIBLE_CLASSNAME } ` : `#${ CAPTCHA_ELEMENT_ID } ` , {
120
- sitekey,
121
- appearance : widgetType === 'always_visible' ? 'always' : 'interaction-only' ,
138
+ sitekey : turnstileSiteKey ,
139
+ appearance : 'interaction-only' ,
122
140
retry : 'never' ,
123
141
'refresh-expired' : 'auto' ,
124
142
callback : function ( token : string ) {
@@ -139,6 +157,10 @@ export const getCaptchaToken = async (captchaOptions: {
139
157
}
140
158
reject ( [ errorCodes . join ( ',' ) , id ] ) ;
141
159
} ,
160
+ 'unsupported-callback' : function ( ) {
161
+ reject ( [ 'This browser is not supported by the CAPTCHA.' , id ] ) ;
162
+ return true ;
163
+ } ,
142
164
} ) ;
143
165
} catch ( e ) {
144
166
/**
@@ -171,5 +193,5 @@ export const getCaptchaToken = async (captchaOptions: {
171
193
}
172
194
}
173
195
174
- return captchaToken ;
196
+ return { captchaToken, captchaWidgetTypeUsed : invisibleWidget ? 'invisible' : 'smart' } ;
175
197
} ;
0 commit comments