mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
✨ feat: 增加脱敏诊断导出便于复制到 Issue
业务页初始化后可在控制台执行 copyEsimDiagnostics(),复制非敏感环境与状态摘要。
This commit is contained in:
@@ -23,6 +23,7 @@ import { t, tl } from '../../js/modules/i18n.js';
|
||||
import { isMobileBrowser, showMobileWarning } from '../../js/modules/browser-utils.js';
|
||||
import captchaManager from '../../js/modules/captcha-manager.js';
|
||||
import Logger from '../../js/modules/logger.js';
|
||||
import { installDiagnosticsGlobal } from '../../js/modules/diagnostics.js';
|
||||
|
||||
class GiffgaffApp {
|
||||
constructor() {
|
||||
@@ -225,6 +226,12 @@ class GiffgaffApp {
|
||||
cookieHandler.startValidityMonitor();
|
||||
}
|
||||
|
||||
// 控制台:copyEsimDiagnostics() 复制脱敏诊断信息到剪贴板(便于贴 Issue)
|
||||
installDiagnosticsGlobal({
|
||||
app: 'giffgaff',
|
||||
getState: () => stateManager.getState()
|
||||
});
|
||||
|
||||
this.initialized = true;
|
||||
Logger.log(t('giffgaff.app.console.initDone'));
|
||||
}
|
||||
|
||||
142
src/js/modules/diagnostics.js
Normal file
142
src/js/modules/diagnostics.js
Normal file
@@ -0,0 +1,142 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 诊断信息导出
|
||||
* 供用户在控制台执行 copyEsimDiagnostics(),将非敏感环境信息复制到 Issue。
|
||||
* 不包含 token / 密码 / cookie / LPA 等敏感字段。
|
||||
*/
|
||||
|
||||
import { copyToClipboard } from './clipboard.js';
|
||||
|
||||
const SENSITIVE_KEY_RE = /token|password|cookie|secret|signature|verifier|lpa|activation|ssn|authorization/i;
|
||||
|
||||
/**
|
||||
* 脱敏对象:剔除敏感键,截断过长字符串
|
||||
* @param {Object} source
|
||||
* @param {number} maxDepth
|
||||
* @returns {Object}
|
||||
*/
|
||||
function sanitizeObject(source, maxDepth = 2) {
|
||||
if (!source || typeof source !== 'object' || maxDepth < 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const out = {};
|
||||
Object.keys(source).forEach((key) => {
|
||||
if (SENSITIVE_KEY_RE.test(key)) {
|
||||
out[key] = '[redacted]';
|
||||
return;
|
||||
}
|
||||
const value = source[key];
|
||||
if (value == null) {
|
||||
out[key] = value;
|
||||
} else if (typeof value === 'string') {
|
||||
out[key] = value.length > 120 ? `${value.slice(0, 120)}…(len=${value.length})` : value;
|
||||
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
out[key] = value;
|
||||
} else if (typeof value === 'object' && maxDepth > 0) {
|
||||
out[key] = sanitizeObject(value, maxDepth - 1);
|
||||
} else {
|
||||
out[key] = `[${typeof value}]`;
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集诊断快照
|
||||
* @param {Object} [options]
|
||||
* @param {string} [options.app] - 应用标识 giffgaff|simyo|home
|
||||
* @param {Object} [options.state] - 可选业务状态(会脱敏)
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function collectDiagnostics(options = {}) {
|
||||
const { app = 'unknown', state = null } = options;
|
||||
const loc = typeof window !== 'undefined' ? window.location : null;
|
||||
const nav = typeof navigator !== 'undefined' ? navigator : null;
|
||||
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
app,
|
||||
page: {
|
||||
href: loc ? loc.href : '',
|
||||
hostname: loc ? loc.hostname : '',
|
||||
pathname: loc ? loc.pathname : '',
|
||||
protocol: loc ? loc.protocol : ''
|
||||
},
|
||||
runtime: {
|
||||
userAgent: nav ? nav.userAgent : '',
|
||||
language: nav ? nav.language : '',
|
||||
online: nav ? nav.onLine : null,
|
||||
cookieEnabled: nav ? nav.cookieEnabled : null,
|
||||
viewport: typeof window !== 'undefined'
|
||||
? { w: window.innerWidth, h: window.innerHeight }
|
||||
: null
|
||||
},
|
||||
state: state ? sanitizeObject(state) : null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化为适合粘贴到 Issue 的文本
|
||||
* @param {Object} [options]
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatDiagnostics(options = {}) {
|
||||
const payload = collectDiagnostics(options);
|
||||
return [
|
||||
'```json',
|
||||
'// eSIM Tools 诊断信息(已脱敏,可直接贴到 Issue)',
|
||||
JSON.stringify(payload, null, 2),
|
||||
'```'
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制诊断信息到剪贴板
|
||||
* @param {Object} [options]
|
||||
* @returns {Promise<string>} 复制的文本
|
||||
*/
|
||||
export async function copyDiagnostics(options = {}) {
|
||||
const text = formatDiagnostics(options);
|
||||
await copyToClipboard(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂到 window,便于控制台:copyEsimDiagnostics()
|
||||
* @param {Object} [options]
|
||||
*/
|
||||
export function installDiagnosticsGlobal(options = {}) {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const getter = typeof options.getState === 'function' ? options.getState : null;
|
||||
const app = options.app || 'unknown';
|
||||
|
||||
window.copyEsimDiagnostics = async function copyEsimDiagnostics() {
|
||||
const state = getter ? getter() : null;
|
||||
const text = await copyDiagnostics({ app, state });
|
||||
// 保留 console:方便用户确认已复制,并在剪贴板失败时仍能手工拷贝
|
||||
console.log('[eSIM Diagnostics] 已复制到剪贴板(敏感字段已脱敏)\n', text);
|
||||
return text;
|
||||
};
|
||||
|
||||
window.__esimDiagnostics = {
|
||||
collect: () => collectDiagnostics({
|
||||
app,
|
||||
state: getter ? getter() : null
|
||||
}),
|
||||
format: () => formatDiagnostics({
|
||||
app,
|
||||
state: getter ? getter() : null
|
||||
}),
|
||||
copy: window.copyEsimDiagnostics
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
collectDiagnostics,
|
||||
formatDiagnostics,
|
||||
copyDiagnostics,
|
||||
installDiagnosticsGlobal
|
||||
};
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import { t, tl } from '../../js/modules/i18n.js';
|
||||
import { isMobileBrowser, showMobileWarning } from '../../js/modules/browser-utils.js';
|
||||
import Logger from '../../js/modules/logger.js';
|
||||
import { installDiagnosticsGlobal } from '../../js/modules/diagnostics.js';
|
||||
|
||||
class SimyoApp {
|
||||
constructor() {
|
||||
@@ -66,6 +67,12 @@ class SimyoApp {
|
||||
// 更新状态显示
|
||||
uiController.updateStatusPanel();
|
||||
|
||||
// 控制台:copyEsimDiagnostics() 复制脱敏诊断信息到剪贴板(便于贴 Issue)
|
||||
installDiagnosticsGlobal({
|
||||
app: 'simyo',
|
||||
getState: () => stateManager.getState()
|
||||
});
|
||||
|
||||
this.initialized = true;
|
||||
Logger.log(t('simyo.app.console.initDone'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user