@@ -4,43 +4,75 @@ let barChartInstance = null;
44
55// 全域 Turnstile 管理器
66const 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
115148document . 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