2025-11-13 19:07:59 +08:00
//
// KBLoginVM . m
//
# import "KBLoginVM.h"
# import "AppleSignInManager.h"
# import "KBNetworkManager.h"
# import "KBAuthManager.h"
2025-12-05 13:49:12 +08:00
# import "FGIAPManager.h"
# import "IAPVerifyTransactionObj.h"
2025-11-13 19:07:59 +08:00
# import "KBAPI.h"
# import "KBUser.h"
2025-12-11 21:00:43 +08:00
# import "KBMyVM.h"
2025-11-13 19:07:59 +08:00
@ interface KBLoginVM ( )
@ property ( atomic , strong , readwrite , nullable ) KBUser * currentUser ;
2025-12-11 21:00:43 +08:00
@ property ( nonatomic , strong ) KBMyVM * myVM ;
2025-11-13 19:07:59 +08:00
@ end
@ implementation KBLoginVM
2025-12-05 13:49:12 +08:00
// / 统 一 初 始 化 内 购 ( 只 执 行 一 次 ) 。 既 可 以 在 “ 启 动 时 已 登 录 ” 的 场 景 调 用 , 也 可 以 在 “ 登 录 成 功 回 调 ” 里 调 用 。
+ ( void ) kb_configureIAPIfNeeded {
static dispatch_once _t onceToken ;
dispatch_once ( & onceToken , ^ {
[ [ FGIAPManager shared ] setConfigureWith : [ IAPVerifyTransactionObj new ] ] ;
} ) ;
}
2025-11-13 19:07:59 +08:00
+ ( instancetype ) shared {
static KBLoginVM * vm ; static dispatch_once _t onceToken ; dispatch_once ( & onceToken , ^ { vm = [ KBLoginVM new ] ; } ) ;
return vm ;
}
- ( BOOL ) isLoggedIn {
return [ [ KBAuthManager shared ] isLoggedIn ] ;
}
- ( void ) signInWithAppleFromViewController : ( UIViewController * ) presenter
completion : ( KBLoginCompletion ) completion {
// 调 起 Apple 登 录
[ [ AppleSignInManager shared ] signInFromViewController : presenter completion : ^ ( ASAuthorizationAppleIDCredential * _Nullable credential , NSError * _Nullable error ) {
if ( error ) { if ( completion ) completion ( NO , error ) ; return ; }
if ( ! [ credential isKindOfClass : [ ASAuthorizationAppleIDCredential class ] ] ) {
2025-11-17 20:26:39 +08:00
if ( completion ) completion ( NO , [ NSError errorWithDomain : @ "KBLogin" code : -1 userInfo : @ { NSLocalizedDescriptionKey : KBLocalized ( @ "Invalid login credential" ) } ] ) ;
2025-11-13 19:07:59 +08:00
return ;
}
ASAuthorizationAppleIDCredential * cred = ( ASAuthorizationAppleIDCredential * ) credential ;
// identityToken / authorizationCode 均 可 按 后 端 要 求 传 递 ; 本 项 目 后 端 使 用 identityToken 映 射 为 code
NSString * identityToken = cred . identityToken ? [ [ NSString alloc ] initWithData : cred . identityToken encoding : NSUTF8StringEncoding ] : nil ;
NSString * authorizationCode = cred . authorizationCode ? [ [ NSString alloc ] initWithData : cred . authorizationCode encoding : NSUTF8StringEncoding ] : nil ;
NSMutableDictionary * params = [ NSMutableDictionary dictionary ] ;
2025-12-02 19:39:37 +08:00
if ( identityToken . length ) params [ @ "identityToken" ] = identityToken ;
2025-11-13 19:07:59 +08:00
if ( authorizationCode . length ) params [ @ "accessCode" ] = authorizationCode ; // 仅 供 后 端 需 要 时 使 用
if ( cred . user . length ) params [ @ "userID" ] = cred . user ; // 可 选
2025-12-04 14:17:47 +08:00
// 如 有 本 地 缓 存 的 性 别 ( 首 次 进 入 首 页 时 选 择 ) , 一 并 上 传 给 后 端
NSNumber * genderNumber = [ self kb_localGenderParamIfAvailable ] ;
if ( genderNumber ! = nil ) {
params [ @ "gender" ] = genderNumber ;
}
2025-12-02 20:33:17 +08:00
[ KBHUD show ] ;
2025-11-13 19:07:59 +08:00
// 向 服 务 端 发 起 校 验
2025-12-03 13:54:57 +08:00
[ [ KBNetworkManager shared ] POST : API_APPLE _LOGIN jsonBody : params headers : nil completion : ^ ( NSDictionary * _Nullable jsonOrData , NSURLResponse * _Nullable response , NSError * _Nullable error ) {
2025-12-02 20:33:17 +08:00
[ KBHUD dismiss ] ;
2025-11-13 19:07:59 +08:00
if ( error ) { if ( completion ) completion ( NO , error ) ; return ; }
// 从 返 回 JSON 中 提 取 token ( 支 持 常 见 命 名 与 层 级 data / user )
// 先 解 析 用 户 模 型 ( 使 用 MJExtension )
2025-12-02 20:33:17 +08:00
NSDictionary * dict = jsonOrData [ @ "data" ] ;
KBUser * user = [ KBUser mj_objectWithKeyValues : dict ] ;
2025-11-13 19:07:59 +08:00
self . currentUser = user ;
2025-12-02 20:33:17 +08:00
if ( user . token . length = = 0 ) {
2025-11-17 20:26:39 +08:00
if ( completion ) completion ( NO , [ NSError errorWithDomain : @ "KBLogin" code : -2 userInfo : @ { NSLocalizedDescriptionKey : KBLocalized ( @ "No token returned" ) } ] ) ;
2025-11-13 19:07:59 +08:00
return ;
}
2025-12-02 20:33:17 +08:00
[ [ KBUserSessionManager shared ] handleLoginSuccessWithUser : user ] ;
2025-12-05 13:49:12 +08:00
[ KBLoginVM kb_configureIAPIfNeeded ] ;
2025-12-11 21:00:43 +08:00
[ self kb_syncKeyboardCharactersAfterLogin ] ;
2025-12-05 13:49:12 +08:00
if ( completion ) completion ( YES , nil ) ;
2025-11-13 19:07:59 +08:00
// 保 存 登 录 态 到 共 享 钥 匙 串 ; 供 App 与 扩 展 共 享
2025-12-02 20:33:17 +08:00
// BOOL ok = [ [ KBAuthManager shared ] saveAccessToken : user . token
// refreshToken : nil
// expiryDate : nil
// userIdentifier : cred . user ] ;
// if ( completion ) completion ( ok , ok ? nil : [ NSError errorWithDomain : @ "KBLogin" code : -3 userInfo : @ { NSLocalizedDescriptionKey : KBLocalized ( @ "Failed to save login state" ) } ] ) ;
2025-11-13 19:07:59 +08:00
} ] ;
} ] ;
}
2025-12-04 13:37:11 +08:00
// / 邮 箱 登 录
- ( void ) emailLoginEmail : ( NSString * ) email password : ( NSString * ) password WithCompletion : ( KBLoginCompletion ) completion ;
{
[ KBHUD show ] ;
NSMutableDictionary * params = [ NSMutableDictionary dictionary ] ;
2025-12-08 16:39:47 +08:00
if ( email . length ) params [ @ "mail" ] = email ;
2025-12-04 13:37:11 +08:00
if ( password . length ) params [ @ "password" ] = password ;
2025-12-04 14:17:47 +08:00
// 如 有 本 地 缓 存 的 性 别 ( 首 次 进 入 首 页 时 选 择 ) , 一 并 上 传 给 后 端
NSNumber * genderNumber = [ self kb_localGenderParamIfAvailable ] ;
if ( genderNumber ! = nil ) {
params [ @ "gender" ] = genderNumber ;
}
2025-12-04 13:37:11 +08:00
// 向 服 务 端 发 起 校 验
[ [ KBNetworkManager shared ] POST : API_EMAIL _LOGIN jsonBody : params headers : nil completion : ^ ( NSDictionary * _Nullable jsonOrData , NSURLResponse * _Nullable response , NSError * _Nullable error ) {
[ KBHUD dismiss ] ;
if ( error ) { if ( completion ) completion ( NO , error ) ; return ; }
NSDictionary * dict = jsonOrData [ @ "data" ] ;
KBUser * user = [ KBUser mj_objectWithKeyValues : dict ] ;
self . currentUser = user ;
if ( user . token . length = = 0 ) {
if ( completion ) completion ( NO , [ NSError errorWithDomain : @ "KBLogin" code : -2 userInfo : @ { NSLocalizedDescriptionKey : KBLocalized ( @ "No token returned" ) } ] ) ;
return ;
}
[ [ KBUserSessionManager shared ] handleLoginSuccessWithUser : user ] ;
2025-12-05 13:49:12 +08:00
[ KBLoginVM kb_configureIAPIfNeeded ] ;
2025-12-11 21:00:43 +08:00
[ self kb_syncKeyboardCharactersAfterLogin ] ;
2025-12-05 13:49:12 +08:00
if ( completion ) completion ( YES , nil ) ;
2025-12-04 15:28:11 +08:00
} ] ;
}
// / 邮 箱 注 册
- ( void ) emailRegisterParams : ( NSDictionary * ) params withCompletion : ( KBRegisterCompletion ) completion {
[ KBHUD show ] ;
// NSMutableDictionary * params = [ NSMutableDictionary dictionary ] ;
// if ( mailAddress . length ) params [ @ "mailAddress" ] = mailAddress ;
// if ( password . length ) params [ @ "password" ] = password ;
// if ( passwordConfirm . length ) params [ @ "passwordConfirm" ] = passwordConfirm ;
//
// // 如 有 本 地 缓 存 的 性 别 ( 首 次 进 入 首 页 时 选 择 ) , 一 并 上 传 给 后 端
// NSNumber * genderNumber = [ self kb_localGenderParamIfAvailable ] ;
// if ( genderNumber ! = nil ) {
// params [ @ "gender" ] = genderNumber ;
// }
// 向 服 务 端 发 起 校 验
[ [ KBNetworkManager shared ] POST : API_EMAIL _REGISTER jsonBody : params headers : nil completion : ^ ( NSDictionary * _Nullable jsonOrData , NSURLResponse * _Nullable response , NSError * _Nullable error ) {
[ KBHUD dismiss ] ;
if ( error ) { if ( completion ) completion ( NO , error ) ; return ; }
2025-12-05 13:49:12 +08:00
if ( completion ) completion ( YES , nil ) ;
2025-12-04 13:37:11 +08:00
} ] ;
}
2025-12-04 19:12:34 +08:00
// / 发 送 验 证 码
- ( void ) sendVerifyMailWithEmail : ( NSString * ) email withCompletion : ( KBVerifyMailCompletion ) completion {
NSMutableDictionary * params = [ NSMutableDictionary dictionary ] ;
if ( email . length ) params [ @ "mailAddress" ] = email ;
2025-12-08 16:39:47 +08:00
[ [ KBNetworkManager shared ] POST : API_SEND _EMAIL _VERIFYMAIL jsonBody : params headers : nil autoShowBusinessError : true completion : ^ ( NSDictionary * _Nullable json , NSURLResponse * _Nullable response , NSError * _Nullable error ) {
2025-12-04 19:12:34 +08:00
} ] ;
}
2025-12-08 16:39:47 +08:00
// / 验 证 验 证 码
- ( void ) verifyEMailCode : ( NSString * ) email verifyCode : ( NSString * ) verifyCode withCompletion : ( KBVerifyMailCompletion ) completion {
NSMutableDictionary * params = [ NSMutableDictionary dictionary ] ;
if ( email . length ) {
params [ @ "mailAddress" ] = email ;
params [ @ "verifyCode" ] = verifyCode ;
}
[ [ KBNetworkManager shared ] POST : API_VERIFY _EMAIL _CODE jsonBody : params headers : nil autoShowBusinessError : true completion : ^ ( NSDictionary * _Nullable json , NSURLResponse * _Nullable response , NSError * _Nullable error ) {
if ( error ) { if ( completion ) completion ( NO , error ) ; return ; }
if ( completion ) completion ( YES , nil ) ;
} ] ;
}
// / 重 置 密 码
- ( void ) resetPassWord : ( NSString * ) email password : ( NSString * ) password confirmPassword : ( NSString * ) confirmPassword withCompletion : ( KBVerifyMailCompletion ) completion ; {
NSMutableDictionary * params = [ NSMutableDictionary dictionary ] ;
params [ @ "mailAddress" ] = email ? email : @ "" ;
params [ @ "password" ] = password ? password : @ "" ;
params [ @ "confirmPassword" ] = confirmPassword ;
[ [ KBNetworkManager shared ] POST : API_RESET _PWD jsonBody : params headers : nil autoShowBusinessError : true completion : ^ ( NSDictionary * _Nullable json , NSURLResponse * _Nullable response , NSError * _Nullable error ) {
if ( error ) { if ( completion ) completion ( NO , error ) ; return ; }
if ( completion ) completion ( YES , nil ) ;
} ] ;
}
2025-12-11 21:00:43 +08:00
# pragma mark - Private
- ( void ) kb_syncKeyboardCharactersAfterLogin {
if ( ! self . myVM ) {
self . myVM = [ [ KBMyVM alloc ] init ] ;
}
[ self . myVM fetchCharacterListByUserWithCompletion : ^ ( NSArray < KBCharacter * > * _Nonnull characterArray , NSError * _Nullable error ) {
if ( error ) {
KBLOG ( @ "[Login] Failed to sync keyboard characters after login: %@" , error ) ;
} else {
KBLOG ( @ "[Login] Synced %tu keyboard characters after login" , characterArray . count ) ;
}
} ] ;
}
2025-11-13 19:07:59 +08:00
# pragma mark - Helpers
// 宽 松 解 析 : token / access_token / accessToken , 支 持 顶 层 或 data / user 层
+ ( NSString * ) tokenFromResponseObject : ( id ) obj {
if ( ! [ obj isKindOfClass : [ NSDictionary class ] ] ) return nil ;
NSDictionary * dict = ( NSDictionary * ) obj ;
NSString * ( ^ pick ) ( NSDictionary * ) = ^ NSString * ( NSDictionary * d ) {
NSArray * keys = @ [ @ "token" , @ "access_token" , @ "accessToken" ] ;
for ( NSString * k in keys ) {
id v = d [ k ] ; if ( [ v isKindOfClass : NSString . class ] && ( ( NSString * ) v ) . length > 0 ) return v ;
}
return nil ;
} ;
NSString * t = pick ( dict ) ;
if ( t . length ) return t ;
id data = dict [ @ "data" ] ; if ( [ data isKindOfClass : NSDictionary . class ] ) { t = pick ( data ) ; if ( t . length ) return t ; }
id user = dict [ @ "user" ] ; if ( [ user isKindOfClass : NSDictionary . class ] ) { t = pick ( user ) ; if ( t . length ) return t ; }
// 扩 展 : 允 许 后 端 将 token 放 在 data . session . token
NSDictionary * d2 = dict [ @ "data" ] ; if ( [ d2 isKindOfClass : NSDictionary . class ] ) {
NSDictionary * session = d2 [ @ "session" ] ; if ( [ session isKindOfClass : NSDictionary . class ] ) { t = pick ( session ) ; if ( t . length ) return t ; }
}
return nil ;
}
2025-12-04 14:17:47 +08:00
// / 读 取 本 地 缓 存 的 性 别 枚 举 值 , 用 于 登 录 接 口 上 传 到 后 端 。
// / - 用 户 在 首 次 进 入 首 页 的 性 别 选 择 页 或 个 人 资 料 页 中 选 择 后 , 会 持 久 化 到 NSUserDefaults 。
- ( nullable NSNumber * ) kb_localGenderParamIfAvailable {
NSUserDefaults * ud = [ NSUserDefaults standardUserDefaults ] ;
// 只 有 在 用 户 真 正 看 过 性 别 选 择 页 后 , 才 认 为 本 地 的 性 别 有 效
BOOL hasShownSexVC = [ ud boolForKey : KBSexSelectShownKey ] ;
if ( ! hasShownSexVC ) {
return nil ;
}
NSInteger value = [ ud integerForKey : KBSexSelectedGenderKey ] ;
if ( value < UserSexMan || value > UserSexTwoSex ) {
return nil ;
}
return @ ( value ) ;
}
2025-11-13 19:07:59 +08:00
@ end