import { apiBase, post } from './api.uts' import { User } from './types.uts' const USER_KEY = 'sf_user' let loginPending: Promise | null = null let sessionReady = false export function loginUser(): Promise { if (loginPending != null) return loginPending as Promise sessionReady = false uni.removeStorageSync(USER_KEY) loginPending = new Promise((resolve, reject) => { uni.login({ provider: 'weixin', success: (res) => { const code = (res as any).code as string if (code == null || code.length == 0) { reject(new Error('微信登录未返回 code')); return } post('/api/auth/wechat/session', { code }).then((session) => { const user = (session as any).user as User openidFromUser(user) setUser(user) sessionReady = true resolve(user) }).catch(reject) }, fail: (err) => reject(new Error(err.errMsg)) }) }).catch((err) => { console.error('[login] failed', err) return Promise.reject(err) }).finally(() => { loginPending = null }) return loginPending as Promise } export function ensureLogin(): Promise { const user = currentUser() if (user != null) return Promise.resolve(user) if (sessionReady) { console.warn('[login] cached user missing; reauthenticating') sessionReady = false } return loginUser() } export function currentUser(): User | null { if (!sessionReady) return null const raw = uni.getStorageSync(USER_KEY) if (raw == null || raw == '') return null return raw as User } export function phoneFromUser(user: any | null): string { if (user == null) return '' const phone = (user as any).phone if (phone == null) return '' if (typeof phone == 'string') return phone as string const obj = phone as any if (obj.Valid == true || obj.valid == true) { if (obj.String != null) return obj.String as string if (obj.string != null) return obj.string as string } return '' } export function openidFromUser(user: any | null): string { if (user == null) throw new Error('请先完成微信登录') const obj = user as any const value = obj.openid != null ? obj.openid : obj.Openid const openid = value == null ? '' : value as string if (openid.length == 0 || openid.indexOf('demo_openid_') == 0 || openid.indexOf('fake_user_') == 0) { throw new Error('登录身份已失效,请重新登录') } return openid } export function setUser(user: User): void { uni.setStorageSync(USER_KEY, user) } export function bindPhoneFromEvent(event: UniEvent): Promise { const detail = (event as any).detail as any const errMsg = detail != null && detail.errMsg != null ? detail.errMsg as string : '' console.log('[phone-auth] getPhoneNumber event', phoneAuthDetailLog(detail)) if (errMsg.indexOf('ok') < 0) { console.log('[phone-auth] getPhoneNumber rejected', { errMsg }) return Promise.reject(new Error('手机号授权未完成')) } const phoneCode = detail != null && detail.code != null ? detail.code as string : '' if (phoneCode.length == 0) { console.log('[phone-auth] getPhoneNumber missing code', phoneAuthDetailLog(detail)) return Promise.reject(new Error('没有拿到手机号授权 code')) } return ensureLogin().then((user) => { const openid = openidFromUser(user) console.log('[phone-auth] backend bind start', { openid: maskIdentifier(openid), hasStoredPhone: phoneFromUser(user).length > 0, phoneCodeLength: phoneCode.length, envVersion: miniProgramEnvVersion(), apiBase: apiBase() }) return post('/api/auth/wechat/phone', { openid, phoneCode }).then((res) => { const nextUser = (res as any).user as User const phone = phoneFromUser(nextUser) if (phone.length == 0) { console.log('[phone-auth] backend bind empty phone', { openid: maskIdentifier(openidFromUser(nextUser)) }) return Promise.reject(new Error('后端未返回已绑定手机号')) } setUser(nextUser) console.log('[phone-auth] backend bind success', { openid: maskIdentifier(openidFromUser(nextUser)), phone: maskPhoneForLog(phone) }) return { user: nextUser, phone } }).catch((err) => { const message = err != null && (err as any).message != null ? (err as any).message as string : '手机号绑定失败' console.log('[phone-auth] backend bind failed', { openid: maskIdentifier(openid), message }) return Promise.reject(err) }) }) } function phoneAuthDetailLog(detail: any | null): any { if (detail == null) { return { errMsg: '', hasCode: false, codeLength: 0, hasEncryptedData: false, hasIv: false, hasCloudID: false } } const code = detail.code != null ? detail.code as string : '' const encryptedData = detail.encryptedData != null ? detail.encryptedData as string : '' const iv = detail.iv != null ? detail.iv as string : '' const cloudID = detail.cloudID != null ? detail.cloudID as string : '' return { errMsg: detail.errMsg != null ? detail.errMsg as string : '', hasCode: code.length > 0, codeLength: code.length, hasEncryptedData: encryptedData.length > 0, hasIv: iv.length > 0, hasCloudID: cloudID.length > 0 } } function maskIdentifier(value: string): string { if (value.length <= 8) return value return value.slice(0, 4) + '...' + value.slice(value.length - 4) } function maskPhoneForLog(phone: string): string { if (phone.length < 7) return phone return phone.slice(0, 3) + '****' + phone.slice(phone.length - 4) } function miniProgramEnvVersion(): string { // #ifdef MP-WEIXIN try { const accountInfo = uni.getAccountInfoSync() const info = accountInfo as any if (info.miniProgram == null) return '' const miniProgram = info.miniProgram as any if (miniProgram.envVersion == null) return '' return miniProgram.envVersion as string } catch (e) { return '' } // #endif return '' }