itube-specs 0.0.445 → 0.0.446
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -41,7 +41,7 @@ import type { IPasswordForm } from '../../types';
|
|
|
41
41
|
import { validateEmail } from '../../runtime';
|
|
42
42
|
import { AuthorizationApiService } from '~/services/api/authorization.service';
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
|
|
45
45
|
|
|
46
46
|
const {t} = useI18n();
|
|
47
47
|
|
|
@@ -76,7 +76,7 @@ import type { IRegistrateForm } from '../../types';
|
|
|
76
76
|
import { validateEmail, validatePassword, validateUsername } from '../../runtime';
|
|
77
77
|
import { AuthorizationApiService } from '~/services/api/authorization.service';
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
|
|
80
80
|
|
|
81
81
|
defineProps<{
|
|
82
82
|
additionalText?: string;
|
|
@@ -115,7 +115,7 @@ import { reportFormsScheme } from '../../lib/report-forms-scheme';
|
|
|
115
115
|
import { EReportFormsSubjects, validateEmail, validatePhone } from '../../runtime';
|
|
116
116
|
import type { InputTypes, IReportForm, IReportRequest } from '../../types';
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
const { initRecaptcha, getRecaptchaToken } = useRecaptcha();
|
|
119
119
|
|
|
120
120
|
const reasonValue = ref('');
|
|
121
121
|
const errorRadio = ref(false);
|
|
@@ -1,75 +1,81 @@
|
|
|
1
|
-
// composables/useRecaptcha.ts
|
|
2
|
-
import { useRuntimeConfig } from '#imports';
|
|
3
|
-
|
|
4
|
-
let recaptchaLoaded = false;
|
|
5
|
-
let siteKey: string | null = null;
|
|
6
|
-
|
|
7
|
-
async function loadRecaptchaScript(): Promise<void> {
|
|
8
|
-
// Всё только на клиенте (SSR-safe)
|
|
9
|
-
if (process.server) return;
|
|
1
|
+
// composables/useRecaptcha.ts (или use-recaptcha.ts для auto-import как useRecaptcha)
|
|
10
2
|
|
|
11
|
-
|
|
3
|
+
import { useRuntimeConfig } from '#imports';
|
|
12
4
|
|
|
13
|
-
|
|
5
|
+
export function useRecaptcha() {
|
|
6
|
+
let recaptchaLoaded = false;
|
|
7
|
+
let siteKey: string | null = null;
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
9
|
+
async function loadRecaptchaScript(): Promise<void> {
|
|
10
|
+
// SSR-safe: только на клиенте
|
|
11
|
+
if (process.server) return;
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
const domain = useRecaptchaNet ? 'recaptcha.net' : 'www.google.com';
|
|
13
|
+
if (recaptchaLoaded) return;
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
script.src = `https://${domain}/recaptcha/api.js?render=${siteKey}&hl=ru`;
|
|
25
|
-
script.async = true;
|
|
26
|
-
script.defer = true;
|
|
27
|
-
document.head.appendChild(script);
|
|
15
|
+
siteKey = useRuntimeConfig().public.recaptchaSiteKey;
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
while (typeof (window as any).grecaptcha === 'undefined' || !(window as any).grecaptcha.execute) {
|
|
34
|
-
if (attempts >= maxAttempts) {
|
|
35
|
-
throw new Error('reCAPTCHA не загрузился (таймаут)');
|
|
17
|
+
if (!siteKey) {
|
|
18
|
+
console.warn('reCAPTCHA siteKey не задан — защита отключена');
|
|
19
|
+
return;
|
|
36
20
|
}
|
|
37
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
38
|
-
attempts++;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
recaptchaLoaded = true;
|
|
42
|
-
|
|
43
|
-
// Опционально: скрываем бейдж (если хотите глобально)
|
|
44
|
-
const style = document.createElement('style');
|
|
45
|
-
style.textContent = '.grecaptcha-badge { visibility: hidden !important; }';
|
|
46
|
-
document.head.appendChild(style);
|
|
47
21
|
|
|
48
|
-
|
|
49
|
-
|
|
22
|
+
const useRecaptchaNet = true; // true = recaptcha.net (для регионов с блокировкой Google)
|
|
23
|
+
const domain = useRecaptchaNet ? 'recaptcha.net' : 'www.google.com';
|
|
24
|
+
|
|
25
|
+
const script = document.createElement('script');
|
|
26
|
+
script.src = `https://${domain}/recaptcha/api.js?render=${siteKey}&hl=ru`;
|
|
27
|
+
script.async = true;
|
|
28
|
+
script.defer = true;
|
|
29
|
+
document.head.appendChild(script);
|
|
30
|
+
|
|
31
|
+
// Polling до готовности grecaptcha
|
|
32
|
+
const maxAttempts = 100; // ~10 сек
|
|
33
|
+
let attempts = 0;
|
|
34
|
+
|
|
35
|
+
while (typeof (window as any).grecaptcha === 'undefined' || !(window as any).grecaptcha.execute) {
|
|
36
|
+
if (attempts >= maxAttempts) {
|
|
37
|
+
throw new Error('reCAPTCHA не загрузился (таймаут)');
|
|
38
|
+
}
|
|
39
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
40
|
+
attempts++;
|
|
41
|
+
}
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
export async function initRecaptcha(): Promise<void> {
|
|
53
|
-
if (process.server) return;
|
|
54
|
-
if (recaptchaLoaded || !siteKey) return;
|
|
43
|
+
recaptchaLoaded = true;
|
|
55
44
|
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
// Скрываем бейдж глобально (один раз)
|
|
46
|
+
const style = document.createElement('style');
|
|
47
|
+
style.textContent = '.grecaptcha-badge { visibility: hidden !important; }';
|
|
48
|
+
document.head.appendChild(style);
|
|
58
49
|
|
|
59
|
-
|
|
60
|
-
export async function getRecaptchaToken(action: string): Promise<string> {
|
|
61
|
-
if (process.server) {
|
|
62
|
-
throw new Error('reCAPTCHA работает только на клиенте');
|
|
50
|
+
console.log('reCAPTCHA v3 загружен глобально');
|
|
63
51
|
}
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
// Инициализация: вызывать в onMounted формы (даёт время на сбор данных о поведении)
|
|
54
|
+
async function initRecaptcha(): Promise<void> {
|
|
55
|
+
if (process.server || recaptchaLoaded || !siteKey) return;
|
|
66
56
|
await loadRecaptchaScript();
|
|
67
57
|
}
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
// Получение токена: вызывать в submit
|
|
60
|
+
async function getRecaptchaToken(action: string): Promise<string> {
|
|
61
|
+
if (process.server) {
|
|
62
|
+
throw new Error('reCAPTCHA работает только на клиенте');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!recaptchaLoaded) {
|
|
66
|
+
await loadRecaptchaScript();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!siteKey) {
|
|
70
|
+
throw new Error('reCAPTCHA siteKey не задан');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// @ts-ignore grecaptcha глобальный
|
|
74
|
+
return await grecaptcha.execute(siteKey, { action });
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
return {
|
|
78
|
+
initRecaptcha,
|
|
79
|
+
getRecaptchaToken,
|
|
80
|
+
};
|
|
75
81
|
}
|