mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-02 22:14:02 +08:00
新用户默认 MFA 时经 v2 verifyOTP 换正式会话;老用户未开启 MFA 时跳过步骤 2。 补齐 v2 代理、5 步 UI、i18n 与单元测试,抽取 requireSessionToken 避免业务接口遗漏守卫。
198 lines
7.0 KiB
TOML
198 lines
7.0 KiB
TOML
[build]
|
||
# 构建命令
|
||
command = "npm run build --silent"
|
||
# 发布目录:仅暴露构建后的 dist 输出
|
||
publish = "dist"
|
||
# Functions目录
|
||
functions = "netlify/functions"
|
||
# Edge Functions 目录
|
||
edge_functions = "netlify/edge-functions"
|
||
|
||
[build.environment]
|
||
# 固定 Node 版本,避免二进制依赖(如 sharp)在 Node 22 下编译耗时
|
||
NODE_VERSION = "20.12.2"
|
||
# 安装时减少无关输出与提示
|
||
NPM_FLAGS = "--no-audit --no-fund --omit=optional"
|
||
|
||
# 重定向和重写规则
|
||
[[redirects]]
|
||
# Giffgaff eSIM页面 - 模块化版本
|
||
from = "/giffgaff"
|
||
to = "/src/giffgaff/giffgaff_modular.html"
|
||
status = 200
|
||
|
||
[[redirects]]
|
||
# Simyo eSIM页面 - 模块化版本
|
||
from = "/simyo"
|
||
to = "/src/simyo/simyo_modular.html"
|
||
status = 200
|
||
|
||
[[redirects]]
|
||
# 根路径重定向到选择页面
|
||
from = "/"
|
||
to = "/index.html"
|
||
status = 200
|
||
|
||
# API代理重定向(用于解决CORS问题)
|
||
# Simyo API v2(须排在 v1 通配之前,例如登录 MFA:security.verifyOTP)
|
||
# 与 src/simyo/js/modules/client-identity.js 保持同步
|
||
[[redirects]]
|
||
from = "/api/simyo/v2/*"
|
||
to = "https://appapi.simyo.nl/webapi/api/v2/:splat"
|
||
status = 200
|
||
force = true
|
||
headers = {X-Forwarded-Host = "appapi.simyo.nl", User-Agent = "MijnSimyoFT/4.28.0 (iOS 18.2; iPhone12,8)"}
|
||
|
||
# 通用 Simyo API 代理 (webapi v1)
|
||
# 代理层覆盖 User-Agent(浏览器无法可靠设置 UA)
|
||
[[redirects]]
|
||
from = "/api/simyo/*"
|
||
to = "https://appapi.simyo.nl/webapi/api/v1/:splat"
|
||
status = 200
|
||
force = true
|
||
headers = {X-Forwarded-Host = "appapi.simyo.nl", User-Agent = "MijnSimyoFT/4.28.0 (iOS 18.2; iPhone12,8)"}
|
||
|
||
# 旧版 simyoapi 路径兼容
|
||
[[redirects]]
|
||
from = "/api/simyo/legacy/*"
|
||
to = "https://appapi.simyo.nl/simyoapi/api/v1/:splat"
|
||
status = 200
|
||
force = true
|
||
headers = {X-Forwarded-Host = "appapi.simyo.nl", User-Agent = "MijnSimyoFT/4.28.0 (iOS 18.2; iPhone12,8)"}
|
||
|
||
[[headers]]
|
||
# 限制 Simyo 代理仅允许指定站点跨域访问
|
||
for = "/api/simyo/*"
|
||
[headers.values]
|
||
Access-Control-Allow-Origin = "https://esim.cosr.eu.org"
|
||
Access-Control-Allow-Headers = "Content-Type, Authorization, Accept, X-Client-Token, X-Client-Platform, X-Client-Version, X-Device-ID, X-Session-Token"
|
||
Access-Control-Allow-Methods = "GET, POST, OPTIONS"
|
||
Vary = "Origin"
|
||
|
||
# 将 /bff/* 映射到 Edge Function bff-proxy
|
||
[[edge_functions]]
|
||
path = "/bff/*"
|
||
function = "bff-proxy"
|
||
|
||
# Markdown for Agents:检测 Accept: text/markdown 请求
|
||
[[edge_functions]]
|
||
path = "/"
|
||
function = "markdown-negotiation"
|
||
|
||
[[redirects]]
|
||
from = "/api/giffgaff/*"
|
||
to = "https://api.giffgaff.com/:splat"
|
||
status = 200
|
||
force = true
|
||
headers = {X-Forwarded-Host = "api.giffgaff.com"}
|
||
|
||
[[headers]]
|
||
for = "/api/giffgaff/*"
|
||
[headers.values]
|
||
# giffgaff 直连不受此限制,保持通用放行
|
||
Access-Control-Allow-Origin = "*"
|
||
Access-Control-Allow-Headers = "Content-Type, Authorization"
|
||
Access-Control-Allow-Methods = "GET, POST, OPTIONS"
|
||
|
||
[[redirects]]
|
||
from = "/api/giffgaff-id/*"
|
||
to = "https://id.giffgaff.com/:splat"
|
||
status = 200
|
||
force = true
|
||
headers = {X-Forwarded-Host = "id.giffgaff.com"}
|
||
|
||
[[headers]]
|
||
for = "/api/giffgaff-id/*"
|
||
[headers.values]
|
||
Access-Control-Allow-Origin = "*"
|
||
Access-Control-Allow-Headers = "Content-Type, Authorization"
|
||
Access-Control-Allow-Methods = "GET, POST, OPTIONS"
|
||
|
||
[[redirects]]
|
||
from = "/api/giffgaff-public/*"
|
||
to = "https://publicapi.giffgaff.com/:splat"
|
||
status = 200
|
||
force = true
|
||
headers = {X-Forwarded-Host = "publicapi.giffgaff.com"}
|
||
|
||
[[headers]]
|
||
for = "/api/giffgaff-public/*"
|
||
[headers.values]
|
||
Access-Control-Allow-Origin = "*"
|
||
Access-Control-Allow-Headers = "Content-Type, Authorization"
|
||
Access-Control-Allow-Methods = "GET, POST, OPTIONS"
|
||
# favicon 根路径重定向(浏览器默认请求 /favicon.ico)
|
||
[[redirects]]
|
||
from = "/favicon.ico"
|
||
to = "/src/assets/favicon.ico"
|
||
status = 200
|
||
|
||
# OG 社交分享图片重定向
|
||
[[redirects]]
|
||
from = "/og-image.png"
|
||
to = "/src/assets/og-image.png"
|
||
status = 200
|
||
|
||
# 处理SPA路由(兜底 404)
|
||
[[redirects]]
|
||
from = "/*"
|
||
to = "/index.html"
|
||
status = 404
|
||
|
||
# 静态资源长期缓存(CSS/JS/字体/图片)
|
||
[[headers]]
|
||
for = "/src/styles/*"
|
||
[headers.values]
|
||
Cache-Control = "public, max-age=31536000, immutable"
|
||
|
||
[[headers]]
|
||
for = "/src/js/*"
|
||
[headers.values]
|
||
Cache-Control = "public, max-age=31536000, immutable"
|
||
|
||
[[headers]]
|
||
for = "/src/assets/*"
|
||
[headers.values]
|
||
Cache-Control = "public, max-age=31536000, immutable"
|
||
|
||
[[headers]]
|
||
for = "/dist/*"
|
||
[headers.values]
|
||
Cache-Control = "public, max-age=31536000, immutable"
|
||
|
||
# HTML 页面:短缓存 + stale-while-revalidate
|
||
[[headers]]
|
||
for = "/*.html"
|
||
[headers.values]
|
||
Cache-Control = "public, max-age=300, stale-while-revalidate=86400"
|
||
|
||
# 安全头部
|
||
[[headers]]
|
||
for = "/*"
|
||
[headers.values]
|
||
X-Frame-Options = "DENY"
|
||
X-XSS-Protection = "1; mode=block"
|
||
X-Content-Type-Options = "nosniff"
|
||
Referrer-Policy = "strict-origin-when-cross-origin"
|
||
# 生产 CSP:包含 unsafe-inline 是因为部分页面仍有内联样式和脚本(如内联 CSS、runtime 兜底脚本),
|
||
# 迁移至 nonce/hash 策略需模板注入机制支持,当前暂保留 unsafe-inline
|
||
# connect-src 必须包含 cdn.jsdelivr.net:Bootstrap CSS/JS 的 sourceMappingURL 会发起 connect
|
||
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google.com https://www.gstatic.com https://browser.sentry-cdn.com https://sentry.io https://*.sentry.io https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com; img-src 'self' data: https:; connect-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://browser.sentry-cdn.com https://www.google-analytics.com https://analytics.google.com https://stats.g.doubleclick.net https://www.googletagmanager.com https://qrcode.show https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com https://www.google.com https://www.gstatic.com https://challenges.cloudflare.com https://sentry.io https://*.sentry.io; font-src 'self' data: https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.gstatic.com; frame-src 'self' https://challenges.cloudflare.com https://www.google.com https://*.sentry.io; worker-src 'self' blob:; child-src 'self' blob:; manifest-src 'self';"
|
||
|
||
# Agent 发现 Link 响应头 (RFC 8288)
|
||
[[headers]]
|
||
for = "/"
|
||
[headers.values]
|
||
Link = "</.well-known/api-catalog>; rel=\"api-catalog\", </.well-known/oauth-protected-resource>; rel=\"oauth-protected-resource\", </.well-known/mcp/server-card.json>; rel=\"mcp-server-card\", </.well-known/agent-skills/index.json>; rel=\"agent-skills\""
|
||
|
||
# Markdown for Agents 支持头部
|
||
[[headers]]
|
||
for = "/"
|
||
[headers.values]
|
||
Vary = "Accept"
|
||
|
||
[[headers]]
|
||
for = "/manifest.webmanifest"
|
||
[headers.values]
|
||
Content-Type = "application/manifest+json; charset=utf-8"
|