Skip to content

Commit 53204b1

Browse files
Merge pull request #12 from alvin000009238/codex/add-cloudflare-turnstile-to-login-and-share-screens-dej8oh
Refactor Turnstile integration to popup verify with robust init and error reporting
2 parents b90a912 + 3884b23 commit 53204b1

1 file changed

Lines changed: 46 additions & 14 deletions

File tree

public/app.js

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,75 @@ let barChartInstance = null;
44

55
// 全域 Turnstile 管理器
66
const GlobalTurnstileManager = {
7-
isEnabled: true,
8-
siteKey: null,
7+
siteKey: '',
98
initPromise: null,
9+
lastError: '',
1010

11-
async init() {
12-
if (this.initPromise) return this.initPromise;
11+
async init(force = false) {
12+
if (this.initPromise && !force) return this.initPromise;
1313

1414
this.initPromise = (async () => {
15+
this.lastError = '';
1516
try {
1617
const res = await fetch('/api/turnstile-site-key');
18+
if (!res.ok) {
19+
this.siteKey = '';
20+
this.lastError = '無法取得 Turnstile 設定';
21+
return;
22+
}
23+
1724
const data = await res.json();
18-
this.siteKey = data.siteKey || '';
19-
if (!this.siteKey) this.isEnabled = false;
25+
this.siteKey = (data.siteKey || '').trim();
26+
if (!this.siteKey) {
27+
this.lastError = 'Turnstile Site Key 未設定';
28+
}
2029
} catch (e) {
2130
console.warn('Failed to init global Turnstile:', e);
22-
this.isEnabled = false;
31+
this.siteKey = '';
32+
this.lastError = '無法連線到驗證服務';
2333
}
2434
})();
2535

2636
return this.initPromise;
2737
},
2838

39+
getLastError() {
40+
return this.lastError;
41+
},
42+
2943
async verify(actionLabel = '此操作') {
30-
await this.init();
31-
if (!this.isEnabled || !this.siteKey) return 'disabled';
44+
// 每次驗證前都嘗試重新抓一次設定,避免首次失敗後永久不可用
45+
await this.init(true);
46+
47+
if (!this.siteKey) {
48+
return '';
49+
}
3250

33-
const waitForTurnstile = () => new Promise((resolve) => {
51+
const waitForTurnstile = (timeoutMs = 5000) => new Promise((resolve, reject) => {
52+
const startedAt = Date.now();
3453
if (typeof turnstile !== 'undefined') return resolve();
54+
3555
const interval = setInterval(() => {
3656
if (typeof turnstile !== 'undefined') {
3757
clearInterval(interval);
3858
resolve();
59+
return;
60+
}
61+
62+
if (Date.now() - startedAt >= timeoutMs) {
63+
clearInterval(interval);
64+
reject(new Error('Turnstile script load timeout'));
3965
}
4066
}, 100);
4167
});
4268

43-
await waitForTurnstile();
69+
try {
70+
await waitForTurnstile();
71+
} catch (e) {
72+
console.warn('Turnstile script not available:', e);
73+
this.lastError = 'Turnstile 腳本載入逾時';
74+
return '';
75+
}
4476

4577
return new Promise((resolve) => {
4678
const overlay = document.createElement('div');
@@ -90,6 +122,7 @@ const GlobalTurnstileManager = {
90122
});
91123
} catch (e) {
92124
console.warn('Failed to render Turnstile popup:', e);
125+
this.lastError = 'Turnstile 元件渲染失敗';
93126
cleanup();
94127
resolve('');
95128
}
@@ -113,7 +146,6 @@ function escapeHTML(str) {
113146
}
114147

115148
document.addEventListener('DOMContentLoaded', () => {
116-
GlobalTurnstileManager.init();
117149
checkDisclaimer();
118150
loadGradesData();
119151
setupSyncFeature();
@@ -803,7 +835,7 @@ function setupSyncFeature() {
803835
// 取得 Turnstile token
804836
const turnstileResponse = await GlobalTurnstileManager.verify('登入');
805837
if (!turnstileResponse) {
806-
showStatus(loginStatus, '請先完成人機驗證', 'error');
838+
showStatus(loginStatus, `請先完成人機驗證${GlobalTurnstileManager.getLastError() || '請重試'})`, 'error');
807839
return;
808840
}
809841

@@ -1067,7 +1099,7 @@ function setupShareFeature() {
10671099
// 取得 Turnstile token
10681100
const turnstileResponse = await GlobalTurnstileManager.verify('建立分享連結');
10691101
if (!turnstileResponse) {
1070-
shareStatus.textContent = '請先完成人機驗證';
1102+
shareStatus.textContent = `請先完成人機驗證${GlobalTurnstileManager.getLastError() || '請重試'})`;
10711103
shareStatus.className = 'status-msg error';
10721104
return;
10731105
}

0 commit comments

Comments
 (0)