/** * OmniCard AI (CRMmp) - 名片主移动端 NFC 安全与远程挂失业务驱动层 * 管理 NFC 资产设备列表、在线/挂失冻结状态流转、防伪 UID 格式校验与安全哈希查验 * 遵循 AGENTS.md 规范与 uts 强类型约束 */ import { NFCRecord } from '../../../common/types.uts'; import { freezeNfcDevice, unfreezeNfcDevice } from '../../../common/api.uts'; import { getCurrentLocale, getOmniText } from '../../../common/locale.uts'; export type SecurityDeviceStatus = 'active' | 'frozen'; export interface NfcSecurityDevice { id: number; uid: string; cardId?: number | null; status: SecurityDeviceStatus; securityHash?: string; batchNo?: string; material?: string; lastScannedAt?: string | null; } export interface SecurityState { devices: NfcSecurityDevice[]; selectedId: number | null; submitting: boolean; errorMessage: string; } export interface SecurityApi { freeze: (params: { id: number; uid?: string }) => Promise; unfreeze: (params: { id: number; uid?: string }) => Promise; } export type SecurityCopyKey = | 'title' | 'desc' | 'statusActive' | 'statusFrozen' | 'freezeBtn' | 'unfreezeBtn' | 'warningTitle' | 'warningDesc' | 'deviceEmpty' | 'uidLabel' | 'batchLabel' | 'antiCounterfeitValid' | 'antiCounterfeitInvalid' | 'errorNotFound' | 'errorAlreadyFrozen' | 'errorNotFrozen' | 'freezeSuccess' | 'unfreezeSuccess' | 'submitting' | 'operationError' | 'shieldTitleActive' | 'shieldDescActive' | 'shieldTitleFrozen' | 'shieldDescFrozen' | 'chipDetailsTitle' | 'securityHashLabel' | 'materialLabel' | 'protocolLabel' | 'policyPoint1' | 'policyPoint2' | 'policyPoint3' | 'confirmFreezeTitle' | 'confirmFreezeDesc' | 'confirmUnfreezeTitle' | 'confirmUnfreezeDesc' | 'confirmBtn' | 'cancelBtn' | 'cardListTitle'; const DEFAULT_SALT = 'OMNICARD_SECURE_SALT_2026'; /** * 规范化 UID:去除冒号、空格、连字符并转大写 */ export function normalizeUid(raw: string | null | undefined): string { if (raw == null) { return ''; } return String(raw).replace(/[\s:-]/g, '').toUpperCase(); } /** * 防伪 UID 格式校验: * 严格校验十六进制字符,仅放行标准 4 字节(8 字符)或 7 字节(14 字符)NFC 芯片 UID */ export function validateNfcUid(raw: string | null | undefined): { valid: boolean; normalizedUid: string; error?: string; } { const normalized = normalizeUid(raw); if (!normalized) { return { valid: false, normalizedUid: '', error: 'UID 不能为空', }; } // 严格检查是否为纯十六进制字符 if (!/^[0-9A-F]+$/.test(normalized)) { return { valid: false, normalizedUid: normalized, error: 'UID 必须为有效十六进制格式', }; } // 校验长度:标准 4 字节 (8 字符) 或 7 字节 (14 字符) if (normalized.length !== 8 && normalized.length !== 14) { return { valid: false, normalizedUid: normalized, error: 'UID 长度不符合标准 NFC 规范 (需为 8 或 14 字符)', }; } return { valid: true, normalizedUid: normalized, }; } /** * 计算确定性防伪安全哈希码 * 基于 UID 与安全盐生成 32 位 Hex 防伪数字签名 */ export function computeSecurityHash( uid: string, salt: string = DEFAULT_SALT ): string { const cleanUid = normalizeUid(uid); if (!cleanUid) { return ''; } const text = cleanUid + ':' + (salt || DEFAULT_SALT); let h1 = 0x811c9dc5; let h2 = 0x9e3779b9; for (let i = 0; i < text.length; i++) { const code = text.charCodeAt(i); h1 = Math.imul(h1 ^ code, 0x01000193); h2 = Math.imul(h2 ^ code, 0x5bd1e995); h1 = (h1 << 13) | (h1 >>> 19); h2 = (h2 << 15) | (h2 >>> 17); } const p1 = (h1 >>> 0).toString(16).padStart(8, '0'); const p2 = (h2 >>> 0).toString(16).padStart(8, '0'); const p3 = ((h1 ^ h2) >>> 0).toString(16).padStart(8, '0'); const p4 = ((h1 + h2) >>> 0).toString(16).padStart(8, '0'); return (p1 + p2 + p3 + p4).toUpperCase(); } /** * 查验防伪安全哈希是否匹配,防止物理克隆伪造 */ export function verifySecurityHash( uid: string, hash: string, salt: string = DEFAULT_SALT ): boolean { if (!uid || !hash) { return false; } const cleanUid = normalizeUid(uid); if (!cleanUid) { return false; } const expected = computeSecurityHash(cleanUid, salt); return expected.toUpperCase() === String(hash).trim().toUpperCase(); } /** * 初始化状态对象 */ export function createSecurityState( initialDevices?: NfcSecurityDevice[] ): SecurityState { const devices = initialDevices ? initialDevices.map((d) => ({ ...d })) : []; const selectedId = devices.length > 0 ? devices[0].id : null; return { devices, selectedId, submitting: false, errorMessage: '', }; } /** * 选中指定设备 */ export function selectDevice(state: SecurityState, id: number): void { state.selectedId = id; state.errorMessage = ''; } /** * 远程挂失冻结设备 (active -> frozen) */ export async function freezeDevice( state: SecurityState, id: number, api?: SecurityApi ): Promise { const device = state.devices.find((d) => d.id === id); if (!device) { throw new Error('NFC 设备不存在'); } if (device.status === 'frozen') { throw new Error('设备已处于挂失冻结状态'); } state.submitting = true; state.errorMessage = ''; try { if (api != null && typeof api.freeze === 'function') { await api.freeze({ id, uid: device.uid }); } else { await freezeNfcDevice({ id, uid: device.uid }); } device.status = 'frozen'; return device; } catch (err) { const message = err instanceof Error ? err.message : '远程挂失失败'; state.errorMessage = message; throw new Error(message); } finally { state.submitting = false; } } /** * 远程解除挂失冻结恢复 (frozen -> active) */ export async function unfreezeDevice( state: SecurityState, id: number, api?: SecurityApi ): Promise { const device = state.devices.find((d) => d.id === id); if (!device) { throw new Error('NFC 设备不存在'); } if (device.status !== 'frozen') { throw new Error('设备未处于挂失冻结状态'); } state.submitting = true; state.errorMessage = ''; try { if (api != null && typeof api.unfreeze === 'function') { await api.unfreeze({ id, uid: device.uid }); } else { await unfreezeNfcDevice({ id, uid: device.uid }); } device.status = 'active'; return device; } catch (err) { const message = err instanceof Error ? err.message : '解除挂失失败'; state.errorMessage = message; throw new Error(message); } finally { state.submitting = false; } } /** * 国际化多语言文案字典解析 */ export function resolveSecurityText(key: SecurityCopyKey, locale?: string): string { const lang = locale != null && String(locale).trim() !== '' ? String(locale).trim() : getCurrentLocale(); const isZh = lang === 'zh-CN'; switch (key) { case 'title': return isZh ? 'NFC 安全与挂失' : 'NFC Security & Remote Lock'; case 'desc': return isZh ? '物理名片丢失或遭遇异常,可立即远程挂失冻结,保护商业资产安全。' : 'Remotely freeze lost cards instantly to prevent unauthorized access.'; case 'statusActive': return isZh ? '正常使用中' : 'Active'; case 'statusFrozen': return isZh ? '已挂失冻结' : 'Frozen'; case 'freezeBtn': return isZh ? '远程挂失' : 'Freeze Card'; case 'unfreezeBtn': return isZh ? '解除挂失恢复' : 'Unfreeze Card'; case 'warningTitle': return isZh ? '安全保护机制' : 'Security Policy'; case 'warningDesc': return isZh ? '挂失后将立即切断该实体 NFC 芯片的触碰跳转与数据传输。如已找回芯片,可在此安全解冻。' : 'Freezing immediately halts touch response and data access on this physical card.'; case 'deviceEmpty': return isZh ? '暂无名下 NFC 硬件设备' : 'No NFC hardware registered'; case 'uidLabel': return isZh ? '芯片 UID' : 'Chip UID'; case 'batchLabel': return isZh ? '出厂批次' : 'Batch No'; case 'antiCounterfeitValid': return isZh ? '防伪验签通过' : 'Authenticity Verified'; case 'antiCounterfeitInvalid': return isZh ? '防伪校验异常' : 'Verification Failed'; case 'errorNotFound': return isZh ? 'NFC 设备不存在' : 'Device not found'; case 'errorAlreadyFrozen': return isZh ? '设备已处于挂失冻结状态' : 'Device already frozen'; case 'errorNotFrozen': return isZh ? '设备未处于挂失冻结状态' : 'Device is not frozen'; case 'freezeSuccess': return isZh ? '设备已成功远程挂失冻结' : 'Card frozen successfully'; case 'unfreezeSuccess': return isZh ? '设备已成功解冻恢复' : 'Card unfrozen successfully'; case 'submitting': return isZh ? '处理中...' : 'Processing...'; case 'operationError': return isZh ? '操作异常' : 'Something went wrong'; case 'shieldTitleActive': return isZh ? '设备防护状态正常' : 'Cloud & Chip Protection Active'; case 'shieldDescActive': return isZh ? '物理近场触碰正常联通,触碰即可即时唤起您的专属微官网。' : 'Physical tap-to-share is active and securely linked.'; case 'shieldTitleFrozen': return isZh ? '物理芯片已远程挂失冻结' : 'Hardware Frozen & Locked'; case 'shieldDescFrozen': return isZh ? '已切断物理触碰与数据传输,微官网与访客雷达资产依然安全隔离。' : 'Physical tap-to-share is blocked. Cloud portal data remains safe.'; case 'chipDetailsTitle': return isZh ? '芯片防伪与技术参数' : 'Chip Authenticity & Technical Specs'; case 'securityHashLabel': return isZh ? '防伪安全哈希' : 'Security Hash'; case 'materialLabel': return isZh ? '物理材质' : 'Material'; case 'protocolLabel': return isZh ? '通信协议' : 'Protocol'; case 'policyPoint1': return isZh ? '立即阻断近场触碰响应,拾获者无法获取名片信息或盗用身份' : 'Instantly halts physical tap-to-share to prevent identity theft'; case 'policyPoint2': return isZh ? '云端微官网、商业资料与雷达轨迹数据不受影响,完全安全留存' : 'Cloud portal data, assets, and radar traces remain intact'; case 'policyPoint3': return isZh ? '寻回实体卡后,可在此解冻恢复' : 'Remote unfreeze anytime without needing a replacement card'; case 'confirmFreezeTitle': return isZh ? '确认远程挂失此实体卡?' : 'Freeze this physical card?'; case 'confirmFreezeDesc': return isZh ? '挂失将立即阻断该实体卡的一切近场触碰功能,拾获者将无法读取任何数据。' : 'Freezing will immediately halt all touch responses for this physical card.'; case 'confirmUnfreezeTitle': return isZh ? '确认解除挂失恢复?' : 'Unfreeze and restore card?'; case 'confirmUnfreezeDesc': return isZh ? '解除挂失后,该实体卡将立即恢复触碰唤起微官网与交换名片功能。' : 'Unfreezing will restore tap-to-share and portal access immediately.'; case 'confirmBtn': return isZh ? '确认执行' : 'Confirm'; case 'cancelBtn': return isZh ? '取消' : 'Cancel'; case 'cardListTitle': return isZh ? 'NFC 设备列表' : 'Registered NFC Cards'; default: return ''; } }