diff --git a/index.html b/index.html index d38ec4d..41bcce7 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + eSIM Tools - Giffgaff & Simyo @@ -357,10 +357,7 @@ - + diff --git a/src/giffgaff/giffgaff_complete_esim.html b/src/giffgaff/giffgaff_complete_esim.html index 3ccafca..0b8868c 100644 --- a/src/giffgaff/giffgaff_complete_esim.html +++ b/src/giffgaff/giffgaff_complete_esim.html @@ -11,7 +11,7 @@ - + Giffgaff eSIM 申请工具 @@ -3648,15 +3648,32 @@ try { if (!window.turnstile || !siteKey) { setTimeout(boot, 300); return; } var c = document.createElement('div'); + c.style.display = 'none'; + c.setAttribute('aria-hidden', 'true'); document.body.appendChild(c); + var isExecuting = false; + var hasExecuted = false; + var markIdle = function() { isExecuting = false; }; + var exec = function() { + if (!window.turnstile || isExecuting) { return; } + isExecuting = true; + if (hasExecuted) { try { window.turnstile.reset(id); } catch (_) {} } + hasExecuted = true; + try { window.turnstile.execute(id); } catch (e) { markIdle(); } + }; var id = window.turnstile.render(c, { sitekey: siteKey, size: 'invisible', - callback: function (token) { window.__cfTurnstileToken = token; } + callback: function (token) { window.__cfTurnstileToken = token; markIdle(); }, + 'error-callback': markIdle, + 'timeout-callback': markIdle, + 'expired-callback': function () { markIdle(); exec(); } }); - var exec = function() { try { window.turnstile.execute(id); } catch (e) {} }; exec(); - setInterval(exec, 110000); + setInterval(function () { + if (document.visibilityState === 'hidden') { return; } + exec(); + }, 110000); } catch (e) { setTimeout(boot, 500); } } if (document.readyState === 'loading') { diff --git a/src/giffgaff/giffgaff_modular.html b/src/giffgaff/giffgaff_modular.html index 5b3004e..9267669 100644 --- a/src/giffgaff/giffgaff_modular.html +++ b/src/giffgaff/giffgaff_modular.html @@ -15,7 +15,7 @@ - + Giffgaff eSIM 申请工具 @@ -764,15 +764,32 @@ try { if (!window.turnstile || !siteKey) { setTimeout(boot, 300); return; } var c = document.createElement('div'); + c.style.display = 'none'; + c.setAttribute('aria-hidden', 'true'); document.body.appendChild(c); + var isExecuting = false; + var hasExecuted = false; + var markIdle = function() { isExecuting = false; }; + var exec = function() { + if (!window.turnstile || isExecuting) { return; } + isExecuting = true; + if (hasExecuted) { try { window.turnstile.reset(id); } catch (_) {} } + hasExecuted = true; + try { window.turnstile.execute(id); } catch (e) { markIdle(); } + }; var id = window.turnstile.render(c, { sitekey: siteKey, size: 'invisible', - callback: function (token) { window.__cfTurnstileToken = token; } + callback: function (token) { window.__cfTurnstileToken = token; markIdle(); }, + 'error-callback': markIdle, + 'timeout-callback': markIdle, + 'expired-callback': function () { markIdle(); exec(); } }); - var exec = function() { try { window.turnstile.execute(id); } catch (e) {} }; exec(); - setInterval(exec, 110000); + setInterval(function () { + if (document.visibilityState === 'hidden') { return; } + exec(); + }, 110000); } catch (e) { setTimeout(boot, 500); } } if (document.readyState === 'loading') { diff --git a/src/js/init-home-i18n.js b/src/js/init-home-i18n.js new file mode 100644 index 0000000..8083343 --- /dev/null +++ b/src/js/init-home-i18n.js @@ -0,0 +1,4 @@ +// 首页国际化入口脚本:避免内联脚本触发 CSP 限制 +import { initI18n } from './modules/i18n.js'; + +initI18n('home'); diff --git a/src/js/main.js b/src/js/main.js index bb937f9..a8c87be 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -18,43 +18,107 @@ window.TURNSTILE_SITE_KEY = (typeof process !== 'undefined' && process.env && pr ? process.env.TURNSTILE_SITE_KEY : '0x4AAAAAABqjeVscZAiqB11B'; + if (window.TURNSTILE_SITE_KEY) { - const s = document.createElement('script'); - s.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'; - s.async = true; - s.defer = true; - document.head.appendChild(s); + const ensureLoaderScript = () => { + if (window.turnstile) { + return true; + } + const selector = 'script[src="https://challenges.cloudflare.com/turnstile/v0/api.js"]'; + const existing = document.querySelector(selector); + if (existing) { + if (!existing.dataset.turnstileLoader) { + existing.dataset.turnstileLoader = 'true'; + } + return false; + } + const script = document.createElement('script'); + script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'; + script.async = true; + script.defer = true; + script.dataset.turnstileLoader = 'true'; + document.head.appendChild(script); + return false; + }; // 定义回调,保存 token 供 /bff/* 请求使用 window.onTurnstileDone = (token) => { window.__cfTurnstileToken = token; }; - // 初始化不可见 Turnstile 并立即执行获取 token + let widgetId = null; + let hasExecuted = false; + let executing = false; + + const markIdle = () => { executing = false; }; + + const handleToken = (token) => { + window.__cfTurnstileToken = token; + markIdle(); + }; + + const runExecute = () => { + if (!window.turnstile || widgetId == null || executing) { + return; + } + executing = true; + if (hasExecuted) { + try { window.turnstile.reset(widgetId); } catch (_) {} + } + hasExecuted = true; + try { + window.turnstile.execute(widgetId); + } catch (_) { + markIdle(); + } + }; + const initTurnstile = () => { if (!window.turnstile) { setTimeout(initTurnstile, 300); return; } const container = document.createElement('div'); + container.style.display = 'none'; + container.setAttribute('aria-hidden', 'true'); document.body.appendChild(container); - const widgetId = window.turnstile.render(container, { + widgetId = window.turnstile.render(container, { sitekey: window.TURNSTILE_SITE_KEY, size: 'invisible', - callback: (token) => { - window.__cfTurnstileToken = token; + callback: handleToken, + 'error-callback': markIdle, + 'timeout-callback': markIdle, + 'expired-callback': () => { + markIdle(); + runExecute(); } }); - // 主动执行以获取 token(并定期刷新) - const exec = () => { try { window.turnstile.execute(widgetId); } catch (_) {} }; - exec(); - setInterval(exec, 110000); // 约每110秒刷新一次 + runExecute(); + setInterval(() => { + if (document.visibilityState === 'hidden') return; + runExecute(); + }, 110000); }; - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initTurnstile); + const boot = () => { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initTurnstile, { once: true }); + } else { + initTurnstile(); + } + }; + + if (ensureLoaderScript()) { + boot(); } else { - initTurnstile(); + const waitForScript = () => { + if (window.turnstile) { + boot(); + return; + } + setTimeout(waitForScript, 150); + }; + waitForScript(); } } diff --git a/src/js/modules/footer.js b/src/js/modules/footer.js index 4d49b60..5547461 100644 --- a/src/js/modules/footer.js +++ b/src/js/modules/footer.js @@ -1,20 +1,65 @@ // 统一页脚组件:自动注入到页面底部 import './config.js'; +import { t, onLocaleChange } from './i18n.js'; -function getCopyrightText() { +function resolveFooterParams() { + const currentYear = new Date().getFullYear(); try { const cfg = window.__APP_CONFIG__ || {}; const brand = cfg.brand || 'eSIM Tools'; - const since = Number(cfg.since) || new Date().getFullYear(); - const current = new Date().getFullYear(); - const yearText = since && since < current ? `${since}–${current}` : `${current}`; - return `© ${yearText} ${brand}. 保留所有权利。`; + const since = Number(cfg.since) || currentYear; + const yearRange = since && since < currentYear ? `${since}–${currentYear}` : `${currentYear}`; + return { brand, yearRange }; } catch (_) { - const current = new Date().getFullYear(); - return `© ${current} eSIM Tools. 保留所有权利。`; + return { brand: 'eSIM Tools', yearRange: `${currentYear}` }; } } +function translateOrFallback(key, fallback, params) { + try { + const value = params ? t(key, params) : t(key); + if (typeof value === 'string' && value !== key) { + return value; + } + } catch (_) {} + return fallback; +} + +function bindGithubLink(link) { + const apply = () => { + const aria = translateOrFallback('footer.github.ariaLabel', '在 GitHub 上查看源码'); + link.setAttribute('aria-label', aria); + }; + apply(); + onLocaleChange(apply); +} + +function bindContainerAria(container) { + const apply = () => { + const aria = translateOrFallback('footer.container.ariaLabel', '版权与声明'); + container.setAttribute('aria-label', aria); + }; + apply(); + onLocaleChange(apply); +} + +function bindCopyright(span, options = {}) { + const custom = typeof options.text === 'string' ? options.text.trim() : ''; + if (custom) { + span.textContent = custom; + return; + } + + const apply = () => { + const params = resolveFooterParams(); + const fallback = `© ${params.yearRange} ${params.brand}. 保留所有权利。`; + span.textContent = translateOrFallback('footer.copyright', fallback, params); + }; + + apply(); + onLocaleChange(apply); +} + export function injectFooter(options = {}) { if (document.querySelector('footer[data-component="app-footer"]')) return; @@ -30,38 +75,38 @@ export function injectFooter(options = {}) { div.style.justifyContent = 'center'; div.style.gap = '12px'; div.style.marginTop = '8px'; - + const span = document.createElement('span'); - span.textContent = options.text || getCopyrightText(); - + bindCopyright(span, options); + const githubLink = document.createElement('a'); githubLink.href = 'https://github.com/Silentely/eSIM-Tools'; githubLink.target = '_blank'; githubLink.rel = 'noopener noreferrer'; - githubLink.setAttribute('aria-label', '在 GitHub 上查看源码'); githubLink.style.display = 'flex'; githubLink.style.alignItems = 'center'; githubLink.style.justifyContent = 'center'; githubLink.style.color = '#6b7280'; githubLink.style.textDecoration = 'none'; githubLink.style.transition = 'color 0.2s ease'; - + const githubIcon = document.createElement('i'); githubIcon.className = 'fab fa-github'; githubIcon.style.fontSize = '18px'; - + githubLink.addEventListener('mouseenter', () => { githubLink.style.color = '#1f2937'; }); - + githubLink.addEventListener('mouseleave', () => { githubLink.style.color = '#6b7280'; }); - + githubLink.appendChild(githubIcon); + bindGithubLink(githubLink); div.appendChild(span); div.appendChild(githubLink); - + return div; }; @@ -75,7 +120,6 @@ export function injectFooter(options = {}) { // 情形 2 / 3:新建组件化 footer const container = document.createElement('footer'); container.setAttribute('role', 'contentinfo'); - container.setAttribute('aria-label', '版权与声明'); container.setAttribute('data-component', 'app-footer'); container.style.cssText = [ 'margin-top:40px', @@ -86,6 +130,7 @@ export function injectFooter(options = {}) { 'font-size:14px' ].join(';'); container.appendChild(createCopyrightNode()); + bindContainerAria(container); const pickTarget = () => { return ( @@ -108,4 +153,4 @@ export function autoInjectFooter() { } else { run(); } -} \ No newline at end of file +} diff --git a/src/js/modules/i18n-data.js b/src/js/modules/i18n-data.js index 624159e..1d25356 100644 --- a/src/js/modules/i18n-data.js +++ b/src/js/modules/i18n-data.js @@ -232,6 +232,9 @@ export const TRANSLATIONS = { 'home.simyo.features.confirm': '安装确认功能', 'home.simyo.cta': '使用 Simyo 工具', 'home.footer.html': ' 本工具仅供个人使用,请遵守相关服务条款。
所有数据处理均在本地进行,保护您的隐私安全。', + 'footer.container.ariaLabel': '版权与声明', + 'footer.github.ariaLabel': '在 GitHub 上查看源码', + 'footer.copyright': '© {yearRange} {brand}。保留所有权利。', 'giffgaff.meta.title': 'Giffgaff eSIM 申请工具', 'giffgaff.meta.description': 'Giffgaff eSIM 工具,支持 OAuth 登录、MFA 验证、二维码生成与设备更换。', @@ -454,6 +457,9 @@ export const TRANSLATIONS = { 'home.simyo.features.confirm': 'Installation confirmation', 'home.simyo.cta': 'Launch Simyo tool', 'home.footer.html': ' For personal use only. Please follow the related terms of service.
All data processing happens locally to keep your privacy safe.', + 'footer.container.ariaLabel': 'Copyright and disclaimer', + 'footer.github.ariaLabel': 'View source on GitHub', + 'footer.copyright': '© {yearRange} {brand}. All rights reserved.', 'giffgaff.meta.title': 'Giffgaff eSIM Application Tool', 'giffgaff.meta.description': 'Giffgaff eSIM toolkit covering OAuth login, MFA verification, QR generation, and device change workflows.', diff --git a/src/simyo/simyo_complete_esim.html b/src/simyo/simyo_complete_esim.html index 3d35f76..8224d60 100644 --- a/src/simyo/simyo_complete_esim.html +++ b/src/simyo/simyo_complete_esim.html @@ -5,7 +5,7 @@ - + Simyo NL eSIM 申请工具 diff --git a/src/simyo/simyo_modular.html b/src/simyo/simyo_modular.html index 63017e2..a0cfd3b 100644 --- a/src/simyo/simyo_modular.html +++ b/src/simyo/simyo_modular.html @@ -13,7 +13,7 @@ - + Simyo NL eSIM 申请工具