mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-07 00:09:31 +08:00
- 修复 `createQRCodeContainer` 函数中 `labels` 参数为 `undefined` 时导致解构崩溃的问题,添加默认空对象 - 简化 QR 码库加载逻辑,移除脚本已存在且加载完成时直接返回的冗余检查 - 移除 `showQRResult` 方法中冗余的 `.catch()` 处理,错误已由 `generateQRCode` 内部统一处理 - 在 Netlify 函数中创建 sanitizedError 对象,避免包含 LPA 字符串的错误信息泄露到 Sentry 日志 - 更新测试用例以匹配重构后的函数名和错误消息,并移除无效的 jest mock 配置
87 lines
2.3 KiB
JavaScript
87 lines
2.3 KiB
JavaScript
'use strict';
|
||
|
||
const QRCode = require('qrcode');
|
||
const { withAuth, validateInput, AuthError } = require('./_shared/middleware');
|
||
|
||
const DEFAULT_QR_SIZE = 300;
|
||
const MIN_QR_SIZE = 200;
|
||
const MAX_QR_SIZE = 600;
|
||
const QR_TIMEOUT_MS = 10000;
|
||
|
||
const qrcodeSchema = {
|
||
data: {
|
||
required: true,
|
||
type: 'string',
|
||
minLength: 1,
|
||
maxLength: 2048
|
||
},
|
||
size: {
|
||
required: false,
|
||
type: 'number'
|
||
}
|
||
};
|
||
|
||
function normalizeSize(size = DEFAULT_QR_SIZE) {
|
||
const normalizedSize = Number(size);
|
||
|
||
if (!Number.isInteger(normalizedSize)) {
|
||
throw new AuthError('size must be an integer', 400);
|
||
}
|
||
|
||
if (normalizedSize < MIN_QR_SIZE || normalizedSize > MAX_QR_SIZE) {
|
||
throw new AuthError(`size must be between ${MIN_QR_SIZE} and ${MAX_QR_SIZE}`, 400);
|
||
}
|
||
|
||
return normalizedSize;
|
||
}
|
||
|
||
function withTimeout(promise, timeoutMs) {
|
||
let timeoutId;
|
||
const timeoutPromise = new Promise((_, reject) => {
|
||
timeoutId = setTimeout(() => {
|
||
reject(new Error(`QR code generation timeout after ${timeoutMs}ms`));
|
||
}, timeoutMs);
|
||
});
|
||
|
||
return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
|
||
}
|
||
|
||
exports.handler = withAuth(async (event, context, { body }) => {
|
||
// 405 优先于 Schema 验证(确保 HTTP 方法错误优先返回)
|
||
if (event.httpMethod !== 'POST') {
|
||
throw new AuthError('Method Not Allowed', 405);
|
||
}
|
||
|
||
// 手动调用 Schema 验证,确保 405 已经检查过
|
||
validateInput(qrcodeSchema, body);
|
||
|
||
const size = normalizeSize(body.size);
|
||
|
||
try {
|
||
const qrcode = await withTimeout(QRCode.toDataURL(body.data, {
|
||
errorCorrectionLevel: 'M',
|
||
margin: 2,
|
||
type: 'image/png',
|
||
width: size
|
||
}), QR_TIMEOUT_MS);
|
||
|
||
return {
|
||
statusCode: 200,
|
||
body: JSON.stringify({
|
||
success: true,
|
||
qrcode
|
||
})
|
||
};
|
||
} catch (error) {
|
||
// 不记录 data,避免 LPA 激活信息进入日志或 Sentry。
|
||
console.error('[qrcode-generate] QR code generation failed:', error.message);
|
||
|
||
// 创建一个不含敏感数据的错误对象,防止 LPA 字符串通过 Sentry 泄露
|
||
const sanitizedError = new Error(error.message);
|
||
sanitizedError.name = error.name;
|
||
sanitizedError.stack = error.stack;
|
||
// 不复制可能包含 body.data 的属性
|
||
throw sanitizedError;
|
||
}
|
||
}, { requireAuth: true });
|