This commit is contained in:
Your Name
2026-04-07 18:13:03 +08:00
parent a780356908
commit fdf714f833
397 changed files with 15086 additions and 1043 deletions
+69
View File
@@ -0,0 +1,69 @@
/**
* 企微 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)
}