Files
zyt/admin/src/utils/wecomOauthPostMessage.ts
T
2026-04-07 18:13:03 +08:00

70 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 企微 WwLoginiframe 内授权完成后通过 postMessage 把「完整回调 URL」交给父窗口,
* 随后官方脚本会执行 window.location.href = url 触发整页刷新。
* 部分环境下刷新后地址栏没有 code,Vue 不会走 URL 分支 → bindWorkWechat 从不发起。
* 在 window 上以 capture 监听 message,抢先解析 code 并 stopImmediatePropagation,避免整页跳转。
*/
const DEFAULT_STATE = 'bind_wxwork'
function isWecomPostMessageOrigin(origin: string): boolean {
return origin.includes('work.weixin.qq.com') || origin.includes('tencent.com')
}
function parseOAuthAccept(
params: URLSearchParams,
wxBindState: string
): { code: string } | null {
const wxCode = (params.get('code') || '').trim()
if (!wxCode) return null
const wxState = (params.get('state') ?? '').trim()
const stateOk =
wxState === wxBindState ||
wxState === 'admin_bind_wx' ||
wxState.toLowerCase() === wxBindState.toLowerCase()
const bindFlagOk = params.get('bind_wxwork') === '1'
const implicitOk = wxState === ''
if (!(stateOk || bindFlagOk || implicitOk)) return null
return { code: wxCode }
}
export type WecomOauthCaptureOptions = {
/** 回调 URL 的 pathname 须包含此片段,如 bind-work-wechat、user/setting */
pathIncludes: string
lockKey: string
wxBindState?: string
onCode: (code: string) => void | Promise<void>
}
/**
* @returns 卸载时调用以 removeEventListener
*/
export function attachWecomOAuthMessageCapture(options: WecomOauthCaptureOptions): () => void {
const wxBindState = options.wxBindState ?? DEFAULT_STATE
const handler = (event: MessageEvent) => {
if (!isWecomPostMessageOrigin(event.origin)) return
const data = event.data
if (typeof data !== 'string' || !/^https?:\/\//i.test(data)) return
let u: URL
try {
u = new URL(data)
} catch {
return
}
if (u.host !== window.location.host) return
if (!u.pathname.includes(options.pathIncludes)) return
const parsed = parseOAuthAccept(new URLSearchParams(u.search), wxBindState)
if (!parsed) return
if (sessionStorage.getItem(options.lockKey) === parsed.code) return
event.stopImmediatePropagation()
sessionStorage.setItem(options.lockKey, parsed.code)
void Promise.resolve(options.onCode(parsed.code)).finally(() => {
sessionStorage.removeItem(options.lockKey)
})
}
window.addEventListener('message', handler, true)
return () => window.removeEventListener('message', handler, true)
}