93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
import Taro from '@tarojs/taro'
|
||
import { wxMiniappLogin, type WxMiniappLoginResponse } from '@/api/auth'
|
||
import { storage } from '@/utils/storage'
|
||
import { WX_OPENID_STORAGE_KEY, FALLBACK_WX_APP_ID } from '@/constants/auth'
|
||
|
||
// 取小程序 appId:正常环境走 accountInfo,异常(部分基座不支持)回退常量
|
||
function resolveAppId(): string {
|
||
try {
|
||
return Taro.getAccountInfoSync().miniProgram.appId || FALLBACK_WX_APP_ID
|
||
} catch {
|
||
return FALLBACK_WX_APP_ID
|
||
}
|
||
}
|
||
|
||
// 登录步骤子状态(http 层把 200 内 Message 附加到返回值 message 字段)
|
||
export type LoginSubState = 'needUserInfo' | 'noMobile' | 'complete'
|
||
|
||
export function getLoginSubState(res: WxMiniappLoginResponse): LoginSubState {
|
||
if (res.message === 'needUserInfo') return 'needUserInfo'
|
||
if (res.message === 'noMobile') return 'noMobile'
|
||
return 'complete'
|
||
}
|
||
|
||
// 微信一键登录主流程:
|
||
// Taro.login 换一次性 code → 组装 appId/openId → 调 miniapp/login → 缓存响应中的 openId
|
||
// 首次登录后端可能分步返回 needUserInfo / noMobile(仅绑定凭证,无 token),逐步授权后重调补全
|
||
export async function performWxLogin(): Promise<WxMiniappLoginResponse> {
|
||
// Taro.login reject 时返回 { errMsg } 形状对象而非 Error,此处归一化为带 message 的 Error
|
||
let code: string
|
||
try {
|
||
const r = await Taro.login()
|
||
code = r.code
|
||
} catch (e) {
|
||
throw new Error(e instanceof Error ? e.message : '微信登录失败')
|
||
}
|
||
if (!code) throw new Error('获取微信登录凭证失败')
|
||
const openId = storage.get<string>(WX_OPENID_STORAGE_KEY) ?? undefined
|
||
console.log('[login] performWxLogin 请求', { appId: resolveAppId(), code, openId })
|
||
const result = await wxMiniappLogin({ appId: resolveAppId(), code, openId })
|
||
if (result.openId) storage.set(WX_OPENID_STORAGE_KEY, result.openId)
|
||
console.log('[login] performWxLogin 结果', getLoginSubState(result), result)
|
||
return result
|
||
}
|
||
|
||
// 重调登录接口的公共尾部:换新 code(一次性)→ 组装参数 → 调用 → 缓存 openId
|
||
async function reloginWith(
|
||
openId: string,
|
||
extra: { iv?: string; encryptedData?: string; phoneCode?: string }
|
||
): Promise<WxMiniappLoginResponse> {
|
||
let code: string
|
||
try {
|
||
const r = await Taro.login()
|
||
code = r.code
|
||
} catch {
|
||
throw new Error('获取微信登录凭证失败,请重试')
|
||
}
|
||
const params = { appId: resolveAppId(), code, openId, ...extra }
|
||
console.log('[login] 重调登录接口', params)
|
||
const result = await wxMiniappLogin(params)
|
||
if (result.openId) storage.set(WX_OPENID_STORAGE_KEY, result.openId)
|
||
console.log('[login] 重调结果', getLoginSubState(result), result)
|
||
return result
|
||
}
|
||
|
||
// needUserInfo 二次授权:getUserProfile 拿 iv/encryptedData → 重调登录接口补用户资料
|
||
export async function completeUserInfoLogin(openId: string): Promise<WxMiniappLoginResponse> {
|
||
let profile: { iv: string; encryptedData: string }
|
||
try {
|
||
const r = await Taro.getUserProfile({ desc: '用于完善会员资料' })
|
||
profile = { iv: r.iv, encryptedData: r.encryptedData }
|
||
} catch (e) {
|
||
throw new Error(e instanceof Error ? e.message : (e as { errMsg?: string })?.errMsg || '授权失败')
|
||
}
|
||
return reloginWith(openId, profile)
|
||
}
|
||
|
||
// noMobile 三次授权:手机号授权按钮回调(open-type=getPhoneNumber)→ 重调登录接口
|
||
// detail 两种形态:新版含 code(动态令牌,后端可直连微信换取手机号);
|
||
// 旧版含 iv/encryptedData(需与 session_key 解密,依赖同一 code 会话)
|
||
export async function completeMobileLogin(
|
||
openId: string,
|
||
detail: { iv: string; encryptedData: string; code?: string; errMsg?: string }
|
||
): Promise<WxMiniappLoginResponse> {
|
||
if (!detail.iv && !detail.code) {
|
||
throw new Error(detail.errMsg || '手机号授权失败')
|
||
}
|
||
return reloginWith(openId, {
|
||
iv: detail.iv,
|
||
encryptedData: detail.encryptedData,
|
||
phoneCode: detail.code
|
||
})
|
||
}
|