wechat-notices.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import { resolveApiBase, resolveWebOrigin } from '@/config/api'
  2. import { request, parseResponseJson } from '@/utils/request'
  3. const LAUNCH_PATH = '/api/public/wechat-notices/oauth/launch'
  4. function httpsHost(raw) {
  5. const match = String(raw || '').match(/^https:\/\/([^/?#]+)/i)
  6. return match ? match[1].toLowerCase() : ''
  7. }
  8. export async function fetchWechatNoticeStatus() {
  9. const response = await request({
  10. url: '/web/wechat-notices/binding',
  11. method: 'GET',
  12. timeout: 15000
  13. })
  14. const result = parseResponseJson(response)
  15. const data = result?.data
  16. if (
  17. response.statusCode !== 200 || result?.code !== 200 || !data ||
  18. ['enabled', 'bound', 'subscribed'].some((key) => typeof data[key] !== 'boolean')
  19. ) {
  20. throw new Error('暂时无法确认服务号关注状态')
  21. }
  22. return { enabled: data.enabled, bound: data.bound, subscribed: data.subscribed }
  23. }
  24. async function bindingRequest(method, path, data) {
  25. const response = await request({ url: `/web/wechat-notices/binding${path}`, method, data, timeout: 15000 })
  26. const result = parseResponseJson(response)
  27. if (response.statusCode !== 200 || result?.code !== 200 || !result?.data) {
  28. throw new Error(typeof result?.message === 'string' ? result.message : '绑定操作失败,请稍后重试')
  29. }
  30. return result.data
  31. }
  32. export function isWechatAuthorizeUrl(value) {
  33. // web-view 只能打开业务域名。这里只接受门户上的授权中转页,不直接打开 open.weixin.qq.com。
  34. if (typeof value !== 'string' || value.length > 300) return false
  35. const match = value.match(/^https:\/\/([^/?#@]+)(\/[^?#]*)\?state=([A-Za-z0-9_-]{20,100})$/)
  36. if (!match || match[2] !== LAUNCH_PATH) return false
  37. const allowed = new Set([httpsHost(resolveWebOrigin()), httpsHost(resolveApiBase())].filter(Boolean))
  38. return allowed.has(match[1].toLowerCase())
  39. }
  40. export async function startWechatBinding() {
  41. const data = await bindingRequest('POST', '/start', { mode: 'mobile' })
  42. if (!/^[A-Za-z0-9_-]{20,100}$/.test(data.sessionId || '') || !isWechatAuthorizeUrl(data.authorizeUrl)) {
  43. throw new Error('授权地址无效,请稍后重试')
  44. }
  45. return { sessionId: data.sessionId, authorizeUrl: data.authorizeUrl }
  46. }
  47. export async function fetchWechatBindingSession(sessionId) {
  48. const data = await bindingRequest('GET', `/session?sessionId=${encodeURIComponent(sessionId)}`)
  49. if (!['waiting', 'scanned', 'confirmed', 'expired'].includes(data.status) || typeof data.subscribed !== 'boolean' ||
  50. (data.status === 'scanned' && !/^\d{6}$/.test(data.verificationCode || ''))) {
  51. throw new Error('授权状态异常,请重新绑定')
  52. }
  53. return { status: data.status, subscribed: data.subscribed, verificationCode: data.verificationCode || '' }
  54. }
  55. export async function confirmWechatBinding(sessionId) {
  56. const data = await bindingRequest('POST', '/confirm', { sessionId })
  57. if (['enabled', 'bound', 'subscribed'].some(key => typeof data[key] !== 'boolean')) {
  58. throw new Error('绑定结果异常,请刷新状态')
  59. }
  60. return { enabled: data.enabled, bound: data.bound, subscribed: data.subscribed }
  61. }