#import "GoogleWrapper.h" #import #import #import "platform/apple/JsbBridgeWrapper.h" #include "application/ApplicationManager.h" #include "application/BaseApplication.h" #include "base/Scheduler.h" #include "engine/BaseEngine.h" @interface GoogleWrapper () @property(nonatomic) BOOL initialized; @property(nonatomic) BOOL loginPending; @end @implementation GoogleWrapper + (instancetype)shared { static GoogleWrapper *instance; static dispatch_once_t once; dispatch_once(&once, ^{ instance = [GoogleWrapper new]; }); return instance; } - (void)registerBridge { // Must be capture-free: Creator 3.8.8's MRC bridge releases this block. static OnScriptEventListener const listener = ^(NSString *payload) { dispatch_async(dispatch_get_main_queue(), ^{ [[GoogleWrapper shared] handlePayload:payload]; }); }; [[JsbBridgeWrapper sharedInstance] addScriptEventListener:@"google.request" listener:listener]; } - (BOOL)handleURL:(NSURL *)url { return [GIDSignIn.sharedInstance handleURL:url]; } - (void)reply:(NSDictionary *)request data:(NSDictionary *)data cancelled:(BOOL)cancelled error:(NSString *)error { NSMutableDictionary *response = [@{@"requestId": request[@"requestId"], @"method": request[@"method"], @"success": @(!error), @"cancelled": @(cancelled)} mutableCopy]; if (data) response[@"data"] = data; if (error) response[@"error"] = @{@"code": @"GOOGLE_IOS_ERROR", @"message": error}; NSData *json = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil]; NSString *payload = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding]; auto app = CC_CURRENT_APPLICATION(); if (!app || !app->getEngine() || !payload) return; app->getEngine()->getScheduler()->performFunctionInCocosThread([payload]() { [[JsbBridgeWrapper sharedInstance] dispatchEventToScript:@"google.response" arg:payload]; }); } - (void)handlePayload:(NSString *)payload { id decoded = [NSJSONSerialization JSONObjectWithData:[payload dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]; if (![decoded isKindOfClass:NSDictionary.class]) return; NSDictionary *request = decoded; if (![request[@"requestId"] isKindOfClass:NSString.class] || ![request[@"method"] isKindOfClass:NSString.class]) return; NSString *method = request[@"method"]; @try { if (self.loginPending) { [self reply:request data:nil cancelled:NO error:@"Google 登录仍在进行中。"]; } else if ([method isEqualToString:@"init"]) { NSDictionary *params = [request[@"params"] isKindOfClass:NSDictionary.class] ? request[@"params"] : @{}; NSString *clientID = [NSBundle.mainBundle objectForInfoDictionaryKey:@"GIDClientID"]; NSString *serverID = params[@"webClientId"]; if (![clientID isKindOfClass:NSString.class] || ![clientID hasSuffix:@".apps.googleusercontent.com"] || [clientID containsString:@"YOUR_"] || ![serverID isKindOfClass:NSString.class] || ![serverID hasSuffix:@".apps.googleusercontent.com"]) { [self reply:request data:nil cancelled:NO error:@"请配置 Info.plist 的 GIDClientID(iOS)及 AppConfig.GoogleID(Web)。"]; return; } NSArray *parts = [[clientID componentsSeparatedByString:@"."] reverseObjectEnumerator].allObjects; NSString *scheme = [parts componentsJoinedByString:@"."]; BOOL found = NO; for (NSDictionary *type in [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]) { if ([type[@"CFBundleURLSchemes"] containsObject:scheme]) found = YES; } if (!found) { [self reply:request data:nil cancelled:NO error:@"Info.plist 缺少 Google iOS Client ID 对应的反向 URL Scheme。"]; return; } GIDSignIn.sharedInstance.configuration = [[GIDConfiguration alloc] initWithClientID:clientID serverClientID:serverID]; self.initialized = YES; [self reply:request data:@{} cancelled:NO error:nil]; } else if (!self.initialized) { [self reply:request data:nil cancelled:NO error:@"Google SDK 未初始化。"]; } else if ([method isEqualToString:@"logout"]) { [GIDSignIn.sharedInstance signOut]; [self reply:request data:@{} cancelled:NO error:nil]; } else if ([method isEqualToString:@"login"]) { UIViewController *presenter = UIApplication.sharedApplication.delegate.window.rootViewController; while (presenter.presentedViewController) presenter = presenter.presentedViewController; if (!presenter || UIApplication.sharedApplication.applicationState != UIApplicationStateActive) { [self reply:request data:nil cancelled:NO error:@"请在游戏前台且系统授权弹窗关闭后重试 Google 登录。"]; return; } self.loginPending = YES; [GIDSignIn.sharedInstance signInWithPresentingViewController:presenter completion:^(GIDSignInResult *result, NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ self.loginPending = NO; BOOL cancelled = [error.domain isEqualToString:kGIDSignInErrorDomain] && error.code == kGIDSignInErrorCodeCanceled; if (error) { [self reply:request data:nil cancelled:cancelled error:cancelled ? nil : error.localizedDescription]; return; } GIDGoogleUser *user = result.user; if (!user.idToken.tokenString.length) { [self reply:request data:nil cancelled:NO error:@"Google 未返回 ID Token。"]; return; } [self reply:request data:@{@"idToken": user.idToken.tokenString, @"userId": user.userID ?: @"", @"displayName": user.profile.name ?: @"", @"email": user.profile.email ?: @""} cancelled:NO error:nil]; }); }]; } else { [self reply:request data:nil cancelled:NO error:@"不支持的 Google 方法。"]; } } @catch (NSException *exception) { if ([method isEqualToString:@"login"]) self.loginPending = NO; [self reply:request data:nil cancelled:NO error:exception.reason ?: @"Google SDK 调用失败。"]; } } @end