Files
eSIM-Tools/index.html
Abner f97d8339e4 feat(auth): 增加 OAuth 2.0 PKCE 令牌交换功能
- 新增 giffgaff-token-exchange.js 函数,用于交换 authorization code 和 access token
- 更新 verify-cookie.js 函数,增加对 cookie 中 access token 的提取和验证
- 修改 giffgaff-graphql.js、giffgaff-mfa-challenge.js 和 giffgaff-mfa-validation.js 函数,支持使用 access token 或 cookie 进行身份验证
- 更新 README.md 和 COOKIE_LOGIN_SETUP.md 文档,说明新的身份验证流程和安全注意事项
2025-08-09 14:39:29 +08:00

390 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; font-src 'self' data:; connect-src 'self' https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com; img-src 'self' data: https:; frame-src 'none';">
<title>eSIM工具 - Giffgaff & Simyo</title>
<!-- 性能优化:预连接与预加载关键资源 -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin>
<link rel="preconnect" href="https://api.qrserver.com" crossorigin>
<link rel="preconnect" href="https://api.giffgaff.com" crossorigin>
<link rel="preconnect" href="https://appapi.simyo.nl" crossorigin>
<!-- 关键CSS优先preload 后备 rel=stylesheet -->
<link rel="preload" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" as="style" onload="this.rel='stylesheet'">
<noscript><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"></noscript>
<!-- Font Awesome直接引入避免 onload 触发器被 CSP 阻断导致样式未生效 -->
<link rel="stylesheet" href="/src/assets/fontawesome/all.min.css">
<!-- Font Awesome 资源路径woff2在 /src/assets/fontawesome/,确保已发布且 CSP 允许 font-src 'self' -->
<!-- 本地样式文件 -->
<link rel="preload" href="/src/styles/design-system.css" as="style" onload="this.rel='stylesheet'">
<link rel="preload" href="/src/styles/animations.css" as="style" onload="this.rel='stylesheet'">
<link rel="stylesheet" href="/src/styles/fa-local.css">
<!-- 提供 SW 路径与版本给 performance.js 使用(避免旧 SW 404 -->
<meta name="sw-path" content="/sw.js">
<meta name="build-rev" content="v1">
<style>
/* Design System: 语义化令牌与统一规范 */
:root{
/* Brand */
--brand-start:#6366f1; /* indigo-500 */
--brand-end:#8b5cf6; /* violet-500 */
--accent-ok:#10b981; /* emerald-500 */
--accent-warn:#f59e0b; /* amber-500 */
--accent-danger:#ef4444; /* red-500 */
/* Surface & Text */
--bg-grad-start:var(--brand-start);
--bg-grad-end:var(--brand-end);
--surface:#ffffff;
--surface-alpha:92%;
--text-primary:#1f2937; /* slate-800 */
--text-secondary:#374151; /* gray-700 */
--text-muted:#6b7280; /* gray-500 */
/* Radius & Elevation & Spacing */
--radius-lg:18px;
--shadow-sm:0 4px 10px rgba(0,0,0,.08);
--shadow-md:0 10px 30px rgba(0,0,0,.10);
--shadow-lg:0 18px 40px rgba(0,0,0,.15);
--ring:0 0 0 3px rgba(99,102,241,.35);
--s1:8px; --s2:12px; --s3:16px; --s4:20px; --s5:24px; --s6:32px;
/* Motion */
--ease-standard:cubic-bezier(.2,.8,.2,1);
--duration-fast:.18s;
--duration-normal:.28s;
}
*{margin:0;padding:0;box-sizing:border-box}
body{
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, 'Helvetica Neue', Arial, 'Noto Sans', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, var(--bg-grad-start) 0%, var(--bg-grad-end) 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: var(--s5);
color: var(--text-primary);
}
.main-container{
background: color-mix(in oklab, white var(--surface-alpha), transparent);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
border-radius: var(--radius-lg);
padding: clamp(20px, 4vw, 40px);
box-shadow: var(--shadow-lg);
max-width: 960px;
width: 100%;
text-align: center;
border: 1px solid color-mix(in oklab, white 70%, transparent);
}
.header{ margin-bottom: var(--s6); }
.header h1{
font-size: clamp(1.9rem, 3vw, 2.6rem);
font-weight: 800;
color: var(--text-primary);
margin-bottom: var(--s2);
letter-spacing: .2px;
display:flex;align-items:center;justify-content:center;gap:10px;
}
.header .subtitle{
font-size: clamp(.95rem, 1.9vw, 1.12rem);
color: var(--text-muted);
margin: 0;
}
.github-btn{
background: #24292e;
border: 1px solid rgba(255,255,255,.18);
font-size: 16px;
color:#fff;
cursor:pointer;
padding: 10px 18px;
display:inline-flex;
align-items:center;
justify-content:center;
border-radius: 999px;
transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard);
gap: 8px;
box-shadow: var(--shadow-sm);
text-decoration: none;
margin-top: var(--s3);
outline: none;
will-change: transform;
}
.github-btn:hover{ transform: translateY(-1px); box-shadow: var(--shadow-md); text-decoration: none; color:#fff; background:#111; }
.github-btn:focus-visible{ outline: none; box-shadow: var(--ring); }
.tools-grid{
display:grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: clamp(16px, 3vw, 28px);
margin-top: var(--s6);
}
@media (max-width: 720px){ .tools-grid{ grid-template-columns: 1fr; } }
.tool-card{
background: var(--surface);
border-radius: var(--radius-lg);
padding: clamp(20px, 3.2vw, 28px);
box-shadow: var(--shadow-md);
transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard);
border: 2px solid transparent;
position: relative;
overflow: hidden;
will-change: transform, box-shadow;
}
.tool-card::before{
content: '';
position: absolute;
inset: 0 0 auto 0;
height: 4px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
}
.tool-card:hover{
transform: translateY(-6px);
box-shadow: var(--shadow-lg);
border-color: color-mix(in oklab, var(--primary) 40%, transparent);
}
.tool-card:focus-within{ box-shadow: var(--ring); }
/* 品牌色注入 */
.giffgaff-card{ --primary:#ff6b00; --secondary:#ff8c42; }
.simyo-card{ --primary:#e74c3c; --secondary:#c0392b; }
.tool-icon{
font-size: clamp(2.2rem, 3.2vw, 2.8rem);
color: var(--primary);
margin-bottom: var(--s3);
}
.tool-title{
font-size: clamp(1.2rem, 2.2vw, 1.4rem);
font-weight: 800;
color: var(--text-primary);
margin-bottom: var(--s2);
}
.tool-description{
color: var(--text-muted);
margin-bottom: var(--s4);
line-height: 1.65;
}
.tool-features{
list-style: none;
padding: 0;
margin-bottom: var(--s4);
text-align: left;
}
.tool-features li{
display: flex;
align-items: baseline;
gap: 10px;
padding: 6px 0;
color: var(--text-secondary);
}
.tool-features li i{ color: var(--primary); flex: 0 0 auto; }
.tool-features li span{ flex: 1 1 auto; }
.tool-btn{
background: linear-gradient(135deg, var(--primary), var(--secondary));
color: #fff;
border: none;
padding: 12px 22px;
border-radius: 999px;
font-size: 1rem;
font-weight: 800;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 10px;
transition: transform var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), filter var(--duration-fast) var(--ease-standard);
box-shadow: 0 6px 16px color-mix(in oklab, var(--primary) 35%, transparent);
outline: none;
will-change: transform, filter;
}
.tool-btn:hover{ transform: translateY(-2px); text-decoration: none; color: #fff; filter: brightness(1.03); }
.tool-btn:focus-visible{ box-shadow: var(--ring); }
.footer{
margin-top: var(--s6);
padding-top: var(--s5);
border-top: 1px solid color-mix(in oklab, #ecf0f1 85%, transparent);
color: #6b7280;
font-size: .94rem;
line-height: 1.6;
text-align: center;
}
/* 入场动画IntersectionObserver 触发,避免 setTimeout 抖动 */
.reveal{ opacity: 0; transform: translateY(12px); transition: opacity .5s var(--ease-standard), transform .5s var(--ease-standard); will-change: opacity, transform; }
.reveal.show{ opacity: 1; transform: translateY(0); }
/* 降低动效:尊重用户的减少动态偏好 */
@media (prefers-reduced-motion: reduce){
.reveal{ transition: none; transform: none; opacity: 1; }
.tool-card, .github-btn, .tool-btn{ transition: none!important; }
}
/* 小屏进一步优化 */
@media (max-width: 360px){
.github-btn{ padding: 9px 14px; font-size: 14px; }
.tool-btn{ padding: 10px 18px; font-size: .95rem; }
}
</style>
<!-- Google tag (gtag.js) 延迟加载 -->
<script defer src="https://www.googletagmanager.com/gtag/js?id=G-HGX38HMNL1"></script>
</head>
<body>
<div class="main-container" role="region" aria-label="eSIM 工具选择">
<div class="header">
<h1><i class="fas fa-sim-card" aria-hidden="true"></i> eSIM 工具集</h1>
<p class="subtitle">轻松管理您的 Giffgaff 与 Simyo eSIM 配置</p>
<a href="https://github.com/Silentely/esim-tools" target="_blank" rel="noopener noreferrer" class="github-btn" aria-label="在新窗口打开 GitHub 仓库">
<i class="fab fa-github" aria-hidden="true"></i> GitHub
</a>
</div>
<div class="tools-grid" aria-live="polite">
<!-- Giffgaff工具卡片 -->
<section class="tool-card giffgaff-card reveal" tabindex="-1" aria-labelledby="giffgaff-title" aria-describedby="giffgaff-desc">
<div class="tool-icon" aria-hidden="true">
<i class="fas fa-mobile-alt"></i>
</div>
<h2 id="giffgaff-title" class="tool-title">Giffgaff eSIM 工具</h2>
<p id="giffgaff-desc" class="tool-description">
完整的 Giffgaff eSIM 申请与管理,支持 OAuth 认证、MFA 验证与 GraphQL API 调用
</p>
<ul class="tool-features">
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>OAuth 2.0 PKCE 认证</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>邮件验证码与 MFA 支持</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>GraphQL API 集成</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>自动生成 eSIM 二维码</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>完善的错误处理</span></li>
</ul>
<a href="/giffgaff" class="tool-btn" aria-describedby="giffgaff-desc">
<i class="fas fa-arrow-right" aria-hidden="true"></i><span>使用 Giffgaff 工具</span>
</a>
</section>
<!-- Simyo工具卡片 -->
<section class="tool-card simyo-card reveal" tabindex="-1" aria-labelledby="simyo-title" aria-describedby="simyo-desc">
<div class="tool-icon" aria-hidden="true">
<i class="fas fa-sim-card"></i>
</div>
<h2 id="simyo-title" class="tool-title">Simyo 工具</h2>
<p id="simyo-desc" class="tool-description">
专为 Simyo NL 用户的 eSIM 管理,支持设备更换与短信验证码处理
</p>
<ul class="tool-features">
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>简单快捷的登录验证</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>设备更换完整流程</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>短信验证码支持</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>一键生成二维码</span></li>
<li><i class="fa-solid fa-check" aria-hidden="true"></i><span>安装确认功能</span></li>
</ul>
<a href="/simyo" class="tool-btn" aria-describedby="simyo-desc">
<i class="fas fa-arrow-right" aria-hidden="true"></i><span>使用 Simyo 工具</span>
</a>
</section>
</div>
<footer class="footer" aria-label="免责声明与隐私说明">
<p>
<i class="fas fa-info-circle me-2" aria-hidden="true"></i>
本工具仅供个人使用,请遵守相关服务条款。<br>
<i class="fas fa-shield-alt me-2" aria-hidden="true"></i>
所有数据处理均在本地进行,保护您的隐私安全。
</p>
</footer>
</div>
<!-- 将 bootstrap 脚本 defer减少阻塞 -->
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<!-- 首页逻辑脚本(无内联) -->
<script defer src="/src/js/home.js"></script>
<!-- 性能优化脚本 -->
<script src="/src/js/performance.js"></script>
<!-- 网络状态指示器样式 -->
<style>
.network-status {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 10000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateX(100%);
transition: transform 0.3s ease;
}
.network-status.success {
background: linear-gradient(135deg, #10b981, #059669);
}
.network-status.warning {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
.network-status.show {
transform: translateX(0);
}
.update-notification {
position: fixed;
bottom: 20px;
right: 20px;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: white;
padding: 16px 20px;
border-radius: 12px;
box-shadow: 0 8px 25px rgba(99, 102, 241, 0.3);
z-index: 10000;
max-width: 300px;
}
.notification-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.touch-active {
transform: scale(0.98) !important;
transition: transform 0.1s ease !important;
}
/* 加载状态样式 */
.loading-content {
background: white;
padding: 30px;
border-radius: 12px;
text-align: center;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.loading-content p {
margin: 0;
color: #6b7280;
font-weight: 500;
}
</style>
</body>
</html>