PluginProbe
The Innovative Form Builder – IvyForms / 0.9
The Innovative Form Builder – IvyForms v0.9
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / services / hcaptcha.ts

hcaptcha.ts in The Innovative Form Builder – IvyForms 0.9, at frontend/src/services/hcaptcha.ts

202 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * hCaptcha Service for Frontend Integration
3 * Handles hCaptcha integration
4 */
5
6 import type { HCaptchaWidgetOptions } from '@/types/hcaptcha/hcaptcha-interface'
7 import { useLabels } from '@/composables/useLabels'
8
9 /**
10 * Helper function to get labels safely (defers useLabels call until runtime)
11 */
12 function getLabel(key: string): string {
13 try {
14 const { getLabel: getLabelFn } = useLabels()
15 return getLabelFn(key)
16 } catch {
17 return key // Fallback if Pinia not ready
18 }
19 }
20
21 export interface HCaptchaInstance {
22 render: (container: string | HTMLElement, options: HCaptchaWidgetOptions) => string
23 execute: (container?: string | HTMLElement) => void
24 reset: (container?: string | HTMLElement) => void
25 remove: (container?: string | HTMLElement) => void
26 getResponse: (container?: string | HTMLElement) => string | undefined
27 }
28
29 declare global {
30 interface Window {
31 hcaptcha?: HCaptchaInstance
32 onHCaptchaLoad?: () => void
33 }
34 }
35
36 export class HCaptchaService {
37 private static instance: HCaptchaService
38 private siteKey: string | null = null
39 private widgetId: string | null = null
40 private scriptLoaded = false
41 private language: string = 'auto'
42
43 static getInstance(): HCaptchaService {
44 if (!HCaptchaService.instance) {
45 HCaptchaService.instance = new HCaptchaService()
46 }
47 return HCaptchaService.instance
48 }
49
50 /**
51 * Initialize hCaptcha with configuration
52 * Note: Assumes hCaptcha script is already loaded by ShortcodeService
53 */
54 async init(siteKey: string, language?: string): Promise<void> {
55 this.siteKey = siteKey
56 this.language = language || 'auto'
57
58 return new Promise((resolve) => {
59 if (window.hcaptcha) {
60 this.scriptLoaded = true
61 resolve()
62 } else {
63 // Set up the onload callback if it doesn't exist
64 if (!window.onHCaptchaLoad) {
65 window.onHCaptchaLoad = () => {
66 this.scriptLoaded = true
67 resolve()
68 }
69 }
70
71 // If script isn't loaded yet, wait a bit and try again
72 setTimeout(() => {
73 if (window.hcaptcha) {
74 this.scriptLoaded = true
75 resolve()
76 } else {
77 console.warn(getLabel('hcaptcha_script_not_found'))
78 resolve() // Resolve anyway to avoid hanging
79 }
80 }, 100)
81 }
82 })
83 }
84
85 /**
86 * Render hCaptcha widget
87 */
88 async render(
89 container: string | HTMLElement,
90 options: Partial<HCaptchaWidgetOptions> = {},
91 ): Promise<string> {
92 if (!window.hcaptcha || !this.siteKey) {
93 throw new Error(getLabel('hcaptcha_not_initialized'))
94 }
95
96 const renderOptions: HCaptchaWidgetOptions = {
97 sitekey: this.siteKey,
98 callback: options.callback,
99 'expired-callback': options['expired-callback'],
100 'error-callback': options['error-callback'],
101 size: options.size || 'normal',
102 }
103
104 return new Promise((resolve) => {
105 this.widgetId = window.hcaptcha!.render(container, renderOptions)
106 resolve(this.widgetId)
107 })
108 }
109
110 /**
111 * Execute hCaptcha (mainly for invisible mode)
112 */
113 async execute(): Promise<string> {
114 if (!window.hcaptcha) {
115 throw new Error(getLabel('hcaptcha_not_initialized'))
116 }
117
118 if (!this.widgetId) {
119 throw new Error(getLabel('hcaptcha_widget_not_initialized'))
120 }
121
122 return new Promise((resolve, reject) => {
123 try {
124 window.hcaptcha!.execute(this.widgetId!)
125
126 // Set timeout to reject if no response in 30 seconds
127 const timeout = setTimeout(() => {
128 reject(new Error(getLabel('hcaptcha_execution_timeout')))
129 }, 30000)
130
131 // Poll for response
132 const checkResponse = () => {
133 const response = this.getResponse()
134 if (response) {
135 clearTimeout(timeout)
136 resolve(response)
137 } else {
138 setTimeout(checkResponse, 100)
139 }
140 }
141 checkResponse()
142 } catch (error) {
143 reject(error)
144 }
145 })
146 }
147
148 /**
149 * Get response from hCaptcha widget
150 */
151 getResponse(): string {
152 if (!window.hcaptcha || !this.widgetId) {
153 return ''
154 }
155
156 return window.hcaptcha.getResponse(this.widgetId) || ''
157 }
158
159 /**
160 * Reset hCaptcha widget
161 */
162 reset(): void {
163 if (window.hcaptcha && this.widgetId) {
164 window.hcaptcha.reset(this.widgetId)
165 }
166 }
167
168 /**
169 * Remove hCaptcha widget
170 */
171 remove(): void {
172 if (window.hcaptcha && this.widgetId) {
173 window.hcaptcha.remove(this.widgetId)
174 this.widgetId = null
175 }
176 }
177
178 /**
179 * Get current widget ID
180 */
181 getWidgetId(): string | null {
182 return this.widgetId
183 }
184
185 /**
186 * Check if hCaptcha is ready
187 */
188 isReady(): boolean {
189 return this.scriptLoaded && !!window.hcaptcha
190 }
191
192 /**
193 * Get current site key
194 */
195 getSiteKey(): string | null {
196 return this.siteKey
197 }
198 }
199
200 // Export singleton instance
201 export const hcaptchaService = HCaptchaService.getInstance()
202