// // KBLoginVM.m // #import "KBLoginVM.h" #import "AppleSignInManager.h" #import "KBNetworkManager.h" #import "KBAuthManager.h" #import "KBAPI.h" #import "KBUser.h" @interface KBLoginVM () @property (atomic, strong, readwrite, nullable) KBUser *currentUser; @end @implementation KBLoginVM + (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]]) { if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-1 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"无效的登录凭证")}]); 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]; if (identityToken.length) params[@"code"] = identityToken; if (authorizationCode.length) params[@"accessCode"] = authorizationCode; // 仅供后端需要时使用 if (cred.user.length) params[@"userID"] = cred.user; // 可选 // 向服务端发起校验 [[KBNetworkManager shared] POST:API_APPLE_LOGIN jsonBody:params headers:nil completion:^(id _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { if (completion) completion(NO, error); return; } // 从返回 JSON 中提取 token(支持常见命名与层级 data/user) // 先解析用户模型(使用 MJExtension) KBUser *user = [KBUser userFromResponseObject:jsonOrData]; self.currentUser = user; NSString *token = user.token ?: [self.class tokenFromResponseObject:jsonOrData]; if (token.length == 0) { if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-2 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"未返回 token")}]); return; } // 保存登录态到共享钥匙串;供 App 与扩展共享 BOOL ok = [[KBAuthManager shared] saveAccessToken:token refreshToken:nil expiryDate:nil userIdentifier:cred.user]; if (completion) completion(ok, ok ? nil : [NSError errorWithDomain:@"KBLogin" code:-3 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"保存登录态失败")}]); }]; }]; } #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; } @end