import { resolveApiBase, resolveWebOrigin } from '@/config/api' import { request, parseResponseJson } from '@/utils/request' const LAUNCH_PATH = '/api/public/wechat-notices/oauth/launch' function httpsHost(raw) { const match = String(raw || '').match(/^https:\/\/([^/?#]+)/i) return match ? match[1].toLowerCase() : '' } export async function fetchWechatNoticeStatus() { const response = await request({ url: '/web/wechat-notices/binding', method: 'GET', timeout: 15000 }) const result = parseResponseJson(response) const data = result?.data if ( response.statusCode !== 200 || result?.code !== 200 || !data || ['enabled', 'bound', 'subscribed'].some((key) => typeof data[key] !== 'boolean') ) { throw new Error('暂时无法确认服务号关注状态') } return { enabled: data.enabled, bound: data.bound, subscribed: data.subscribed } } async function bindingRequest(method, path, data) { const response = await request({ url: `/web/wechat-notices/binding${path}`, method, data, timeout: 15000 }) const result = parseResponseJson(response) if (response.statusCode !== 200 || result?.code !== 200 || !result?.data) { throw new Error(typeof result?.message === 'string' ? result.message : '绑定操作失败,请稍后重试') } return result.data } export function isWechatAuthorizeUrl(value) { // web-view 只能打开业务域名。这里只接受门户上的授权中转页,不直接打开 open.weixin.qq.com。 if (typeof value !== 'string' || value.length > 300) return false const match = value.match(/^https:\/\/([^/?#@]+)(\/[^?#]*)\?state=([A-Za-z0-9_-]{20,100})$/) if (!match || match[2] !== LAUNCH_PATH) return false const allowed = new Set([httpsHost(resolveWebOrigin()), httpsHost(resolveApiBase())].filter(Boolean)) return allowed.has(match[1].toLowerCase()) } export async function startWechatBinding() { const data = await bindingRequest('POST', '/start', { mode: 'mobile' }) if (!/^[A-Za-z0-9_-]{20,100}$/.test(data.sessionId || '') || !isWechatAuthorizeUrl(data.authorizeUrl)) { throw new Error('授权地址无效,请稍后重试') } return { sessionId: data.sessionId, authorizeUrl: data.authorizeUrl } } export async function fetchWechatBindingSession(sessionId) { const data = await bindingRequest('GET', `/session?sessionId=${encodeURIComponent(sessionId)}`) if (!['waiting', 'scanned', 'confirmed', 'expired'].includes(data.status) || typeof data.subscribed !== 'boolean' || (data.status === 'scanned' && !/^\d{6}$/.test(data.verificationCode || ''))) { throw new Error('授权状态异常,请重新绑定') } return { status: data.status, subscribed: data.subscribed, verificationCode: data.verificationCode || '' } } export async function confirmWechatBinding(sessionId) { const data = await bindingRequest('POST', '/confirm', { sessionId }) if (['enabled', 'bound', 'subscribed'].some(key => typeof data[key] !== 'boolean')) { throw new Error('绑定结果异常,请刷新状态') } return { enabled: data.enabled, bound: data.bound, subscribed: data.subscribed } }