#!/usr/bin/env node /** * 验证双向跳转脚本逻辑(与 index.html / qiming config 内联脚本一致) * 用法: node scripts/verify-redirect-logic.mjs */ const agentDetailPathMobile = '/pages/agent-detail/agent-detail'; const chatTempPathMobile = '/pages/chat-temp/chat-temp'; const newAgentDetailPathMobile = '/subpackages' + agentDetailPathMobile; const newChatTempPathMobile = '/subpackages' + chatTempPathMobile; // 应用详情页路径 const appDetailsPathMobile = '/subpackages/pages/app-details/app-details'; function runRedirectLogic(protocol, host, href, hash, userAgent) { const baseUrl = protocol + '//' + host; const isMobile = /Android|iPhone|iPad|iPod|Mobile|Tablet/i.test(userAgent); let replaceUrl = null; const location = { get protocol() { return protocol; }, get host() { return host; }, get href() { return href; }, get hash() { return hash; }, replace(url) { replaceUrl = url; }, }; // 兼容分包前的页面 if (isMobile && href.includes('/m/#/pages/')) { let mHash = hash; if (hash.indexOf(agentDetailPathMobile) !== -1) { mHash = mHash.replace(agentDetailPathMobile, newAgentDetailPathMobile); replaceUrl = baseUrl + '/m/' + mHash; return replaceUrl; } if (hash.indexOf(chatTempPathMobile) !== -1) { mHash = mHash.replace(chatTempPathMobile, newChatTempPathMobile); replaceUrl = baseUrl + '/m/' + mHash; return replaceUrl; } } // PC 端访问 M 页面 => 跳转 PC if (!isMobile && href.includes('/m/')) { // 智能体详情页 if (hash && hash.indexOf(agentDetailPathMobile) !== -1) { const matchId = hash.match(new RegExp('[?&]id=([^&#]+)')); const matchConversationId = hash.match(new RegExp('[?&]conversationId=([^&#]+)')); if (matchId && matchId[1]) { const agentId = matchId[1]; if (matchConversationId && matchConversationId[1]) { const conversationId = matchConversationId[1]; replaceUrl = baseUrl + '/home/chat/' + conversationId + '/' + agentId; } else { replaceUrl = baseUrl + '/agent/' + agentId; } return replaceUrl; } } // 应用详情页 if (hash && hash.indexOf(appDetailsPathMobile) !== -1) { const matchId = hash.match(new RegExp('[?&]id=([^&#]+)')); const matchConversationId = hash.match(new RegExp('[?&]conversationId=([^&#]+)')); if (matchId && matchId[1]) { const agentId = matchId[1]; if (matchConversationId && matchConversationId[1]) { const conversationId = matchConversationId[1]; replaceUrl = baseUrl + '/app/chat/' + agentId + '/' + conversationId; } else { replaceUrl = baseUrl + '/app/' + agentId; } return replaceUrl; } } // 临时会话页 if (hash && hash.indexOf(chatTempPathMobile) !== -1) { const matchChatTemp = hash.match(new RegExp('[?&]chatKey=([^&#]+)')); if (matchChatTemp && matchChatTemp[1]) { replaceUrl = baseUrl + '/chat-temp/' + matchChatTemp[1]; return replaceUrl; } } replaceUrl = baseUrl + '/'; return replaceUrl; } // 移动端访问 PC 页面 => 跳转 M if (isMobile && !href.includes('/m/')) { // 智能体详情页 const matchAgent = href.match(new RegExp('/agent/([^/?#]+)')); if (matchAgent) { replaceUrl = baseUrl + '/m/#' + newAgentDetailPathMobile + '?id=' + matchAgent[1]; return replaceUrl; } // 应用会话页 (这里必须要放在应用详情页匹配之前,否则会匹配到应用详情页) const matchAppChat = href.match(new RegExp('app/chat/([^/]+)/([^/]+)')); if (matchAppChat) { const agentId = matchAppChat[1]; const conversationId = matchAppChat[2]; replaceUrl = baseUrl + '/m/#' + appDetailsPathMobile + '?id=' + agentId + '&conversationId=' + conversationId; return replaceUrl; } // 应用详情页 const matchAppDetails = href.match(new RegExp('/app/([^/?#]+)')); if (matchAppDetails) { replaceUrl = baseUrl + '/m/#' + appDetailsPathMobile + '?id=' + matchAppDetails[1]; return replaceUrl; } // 临时会话页 const matchChatTemp = href.match(new RegExp('/chat-temp/([^/?#]+)')); if (matchChatTemp) { replaceUrl = baseUrl + '/m/#' + newChatTempPathMobile + '?chatKey=' + matchChatTemp[1]; return replaceUrl; } const matchHomeChat = href.match(new RegExp('home/chat/([^/]+)/([^/]+)')); if (matchHomeChat) { const conversationId = matchHomeChat[1]; const agentId = matchHomeChat[2]; replaceUrl = baseUrl + '/m/#' + newAgentDetailPathMobile + '?id=' + agentId + '&conversationId=' + conversationId; return replaceUrl; } replaceUrl = baseUrl + '/m/'; return replaceUrl; } return replaceUrl; } const base = 'https://testagent.xspaceagi.com'; const cases = [ { name: '移动端打开 H5 会话详情 /home/chat/1534879/1602 → M 端会话页', protocol: 'https:', host: 'testagent.xspaceagi.com', href: base + '/home/chat/1534879/1602', hash: '', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15', expect: base + '/m/#/subpackages/pages/agent-detail/agent-detail?id=1602&conversationId=1534879', }, { name: 'PC 打开 M 端会话链接(带 conversationId)→ /home/chat/conversationId/agentId', protocol: 'https:', host: 'testagent.xspaceagi.com', href: base + '/m/#/subpackages/pages/agent-detail/agent-detail?id=1602&conversationId=1534879', hash: '#/subpackages/pages/agent-detail/agent-detail?id=1602&conversationId=1534879', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0', expect: base + '/home/chat/1534879/1602', }, { name: 'PC 打开 M 端仅智能体(无 conversationId)→ /agent/agentId', protocol: 'https:', host: 'testagent.xspaceagi.com', href: base + '/m/#/subpackages/pages/agent-detail/agent-detail?id=1602', hash: '#/subpackages/pages/agent-detail/agent-detail?id=1602', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0', expect: base + '/agent/1602', }, { name: '移动端打开 /agent/1602 → M 端智能体详情(无会话)', protocol: 'https:', host: 'testagent.xspaceagi.com', href: base + '/agent/1602', hash: '', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)', expect: base + '/m/#/subpackages/pages/agent-detail/agent-detail?id=1602', }, { name: '移动端打开未知 PC 路径 → /m/ 首页', protocol: 'https:', host: 'testagent.xspaceagi.com', href: base + '/other', hash: '', userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)', expect: base + '/m/', }, ]; let passed = 0; let failed = 0; for (const c of cases) { const got = runRedirectLogic(c.protocol, c.host, c.href, c.hash, c.userAgent); const ok = got === c.expect; if (ok) { passed++; console.log('✓', c.name); } else { failed++; console.log('✗', c.name); console.log(' 期望:', c.expect); console.log(' 实际:', got); } } console.log('\n' + passed + ' 通过, ' + failed + ' 失败'); process.exit(failed > 0 ? 1 : 0);