GoogleWrapper.mm 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. #import "GoogleWrapper.h"
  2. #import <UIKit/UIKit.h>
  3. #import <GoogleSignIn/GoogleSignIn.h>
  4. #import "platform/apple/JsbBridgeWrapper.h"
  5. #include "application/ApplicationManager.h"
  6. #include "application/BaseApplication.h"
  7. #include "base/Scheduler.h"
  8. #include "engine/BaseEngine.h"
  9. @interface GoogleWrapper ()
  10. @property(nonatomic) BOOL initialized;
  11. @property(nonatomic) BOOL loginPending;
  12. @end
  13. @implementation GoogleWrapper
  14. + (instancetype)shared {
  15. static GoogleWrapper *instance;
  16. static dispatch_once_t once;
  17. dispatch_once(&once, ^{ instance = [GoogleWrapper new]; });
  18. return instance;
  19. }
  20. - (void)registerBridge {
  21. // Must be capture-free: Creator 3.8.8's MRC bridge releases this block.
  22. static OnScriptEventListener const listener = ^(NSString *payload) {
  23. dispatch_async(dispatch_get_main_queue(), ^{ [[GoogleWrapper shared] handlePayload:payload]; });
  24. };
  25. [[JsbBridgeWrapper sharedInstance] addScriptEventListener:@"google.request" listener:listener];
  26. }
  27. - (BOOL)handleURL:(NSURL *)url { return [GIDSignIn.sharedInstance handleURL:url]; }
  28. - (void)reply:(NSDictionary *)request data:(NSDictionary *)data cancelled:(BOOL)cancelled error:(NSString *)error {
  29. NSMutableDictionary *response = [@{@"requestId": request[@"requestId"], @"method": request[@"method"],
  30. @"success": @(!error), @"cancelled": @(cancelled)} mutableCopy];
  31. if (data) response[@"data"] = data;
  32. if (error) response[@"error"] = @{@"code": @"GOOGLE_IOS_ERROR", @"message": error};
  33. NSData *json = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil];
  34. NSString *payload = [[NSString alloc] initWithData:json encoding:NSUTF8StringEncoding];
  35. auto app = CC_CURRENT_APPLICATION();
  36. if (!app || !app->getEngine() || !payload) return;
  37. app->getEngine()->getScheduler()->performFunctionInCocosThread([payload]() {
  38. [[JsbBridgeWrapper sharedInstance] dispatchEventToScript:@"google.response" arg:payload];
  39. });
  40. }
  41. - (void)handlePayload:(NSString *)payload {
  42. id decoded = [NSJSONSerialization JSONObjectWithData:[payload dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
  43. if (![decoded isKindOfClass:NSDictionary.class]) return;
  44. NSDictionary *request = decoded;
  45. if (![request[@"requestId"] isKindOfClass:NSString.class] || ![request[@"method"] isKindOfClass:NSString.class]) return;
  46. NSString *method = request[@"method"];
  47. @try {
  48. if (self.loginPending) {
  49. [self reply:request data:nil cancelled:NO error:@"Google 登录仍在进行中。"];
  50. } else if ([method isEqualToString:@"init"]) {
  51. NSDictionary *params = [request[@"params"] isKindOfClass:NSDictionary.class] ? request[@"params"] : @{};
  52. NSString *clientID = [NSBundle.mainBundle objectForInfoDictionaryKey:@"GIDClientID"];
  53. NSString *serverID = params[@"webClientId"];
  54. if (![clientID isKindOfClass:NSString.class] || ![clientID hasSuffix:@".apps.googleusercontent.com"] ||
  55. [clientID containsString:@"YOUR_"] || ![serverID isKindOfClass:NSString.class] ||
  56. ![serverID hasSuffix:@".apps.googleusercontent.com"]) {
  57. [self reply:request data:nil cancelled:NO error:@"请配置 Info.plist 的 GIDClientID(iOS)及 AppConfig.GoogleID(Web)。"];
  58. return;
  59. }
  60. NSArray *parts = [[clientID componentsSeparatedByString:@"."] reverseObjectEnumerator].allObjects;
  61. NSString *scheme = [parts componentsJoinedByString:@"."];
  62. BOOL found = NO;
  63. for (NSDictionary *type in [NSBundle.mainBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]) {
  64. if ([type[@"CFBundleURLSchemes"] containsObject:scheme]) found = YES;
  65. }
  66. if (!found) {
  67. [self reply:request data:nil cancelled:NO error:@"Info.plist 缺少 Google iOS Client ID 对应的反向 URL Scheme。"];
  68. return;
  69. }
  70. GIDSignIn.sharedInstance.configuration = [[GIDConfiguration alloc] initWithClientID:clientID serverClientID:serverID];
  71. self.initialized = YES;
  72. [self reply:request data:@{} cancelled:NO error:nil];
  73. } else if (!self.initialized) {
  74. [self reply:request data:nil cancelled:NO error:@"Google SDK 未初始化。"];
  75. } else if ([method isEqualToString:@"logout"]) {
  76. [GIDSignIn.sharedInstance signOut];
  77. [self reply:request data:@{} cancelled:NO error:nil];
  78. } else if ([method isEqualToString:@"login"]) {
  79. UIViewController *presenter = UIApplication.sharedApplication.delegate.window.rootViewController;
  80. while (presenter.presentedViewController) presenter = presenter.presentedViewController;
  81. if (!presenter || UIApplication.sharedApplication.applicationState != UIApplicationStateActive) {
  82. [self reply:request data:nil cancelled:NO error:@"请在游戏前台且系统授权弹窗关闭后重试 Google 登录。"];
  83. return;
  84. }
  85. self.loginPending = YES;
  86. [GIDSignIn.sharedInstance signInWithPresentingViewController:presenter completion:^(GIDSignInResult *result, NSError *error) {
  87. dispatch_async(dispatch_get_main_queue(), ^{
  88. self.loginPending = NO;
  89. BOOL cancelled = [error.domain isEqualToString:kGIDSignInErrorDomain] && error.code == kGIDSignInErrorCodeCanceled;
  90. if (error) {
  91. [self reply:request data:nil cancelled:cancelled error:cancelled ? nil : error.localizedDescription];
  92. return;
  93. }
  94. GIDGoogleUser *user = result.user;
  95. if (!user.idToken.tokenString.length) {
  96. [self reply:request data:nil cancelled:NO error:@"Google 未返回 ID Token。"];
  97. return;
  98. }
  99. [self reply:request data:@{@"idToken": user.idToken.tokenString,
  100. @"userId": user.userID ?: @"", @"displayName": user.profile.name ?: @"",
  101. @"email": user.profile.email ?: @""} cancelled:NO error:nil];
  102. });
  103. }];
  104. } else {
  105. [self reply:request data:nil cancelled:NO error:@"不支持的 Google 方法。"];
  106. }
  107. } @catch (NSException *exception) {
  108. if ([method isEqualToString:@"login"]) self.loginPending = NO;
  109. [self reply:request data:nil cancelled:NO error:exception.reason ?: @"Google SDK 调用失败。"];
  110. }
  111. }
  112. @end