GoogleAndroidImpl.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { native } from "cc";
  2. import { GoogleImpl, IGoogleLoginRes } from "./GoogleImpl";
  3. import AppConfig from "@scripts/app/AppConfig";
  4. const REQUEST_EVENT = "google.request";
  5. const RESPONSE_EVENT = "google.response";
  6. const DEFAULT_TIMEOUT_MS = 15_000;
  7. const LOGIN_TIMEOUT_MS = 180_000;
  8. // const GOOGLE_CLIENTID = "984811369314-tgai3uj7lb3dmvpuisehm7fb98o60vsq.apps.googleusercontent.com";
  9. interface NativeResponse<T = Record<string, never>> {
  10. requestId: string;
  11. method: string;
  12. success: boolean;
  13. cancelled?: boolean;
  14. data?: T;
  15. error?: {
  16. code?: string;
  17. message?: string;
  18. };
  19. }
  20. interface GoogleLoginData {
  21. idToken?: string;
  22. email?: string;
  23. displayName?: string;
  24. givenName?: string;
  25. familyName?: string;
  26. profilePictureUrl?: string;
  27. }
  28. interface PendingRequest {
  29. method: string;
  30. resolve: (response: NativeResponse<any>) => void;
  31. reject: (error: Error) => void;
  32. timer: ReturnType<typeof setTimeout>;
  33. }
  34. export class GoogleAndroidImpl extends GoogleImpl {
  35. private _requestSeed = 0;
  36. private _pendingRequests = new Map<string, PendingRequest>();
  37. private _initPromise: Promise<boolean> | null = null;
  38. private readonly _responseListener = (payload: string) => this.onNativeResponse(payload);
  39. init(): void {
  40. if (this._initPromise) return;
  41. native.jsbBridgeWrapper.addNativeEventListener(RESPONSE_EVENT, this._responseListener);
  42. this._initPromise = this.request("init", { webClientId: AppConfig.GoogleID })
  43. .then(() => {
  44. this.onInited();
  45. return true;
  46. })
  47. .catch((error) => {
  48. console.error("[Google][Android] 初始化失败:", error);
  49. return false;
  50. });
  51. }
  52. protected async onLogin(): Promise<IGoogleLoginRes> {
  53. if (!(await this.ensureInitialized())) {
  54. return this.loginFailure("Google Android 登录尚未初始化。");
  55. }
  56. try {
  57. const response = await this.request<GoogleLoginData>(
  58. "login",
  59. {},
  60. LOGIN_TIMEOUT_MS,
  61. );
  62. if (response.cancelled) {
  63. return {
  64. accessToken: "",
  65. isCancel: true,
  66. status: "cancelled",
  67. };
  68. }
  69. const idToken = response.data?.idToken || "";
  70. if (!idToken) {
  71. return this.loginFailure("Google 登录未返回 ID Token。");
  72. }
  73. return {
  74. // 与 H5 保持一致:accessToken 字段承载需要交给服务端验证的 Google ID Token。
  75. accessToken: idToken,
  76. isCancel: false,
  77. status: "connected",
  78. };
  79. } catch (error) {
  80. return this.loginFailure(this.errorMessage(error));
  81. }
  82. }
  83. async logOut(): Promise<boolean> {
  84. if (!(await this.ensureInitialized())) return false;
  85. try {
  86. await this.request("logout");
  87. return true;
  88. } catch (error) {
  89. console.error("[Google][Android] 登出失败:", error);
  90. return false;
  91. }
  92. }
  93. private async ensureInitialized(): Promise<boolean> {
  94. if (!this._initPromise) this.init();
  95. return this._initPromise!;
  96. }
  97. private request<T = Record<string, never>>(
  98. method: string,
  99. params: Record<string, unknown> = {},
  100. timeoutMs = DEFAULT_TIMEOUT_MS,
  101. ): Promise<NativeResponse<T>> {
  102. const requestId = `${Date.now()}-${++this._requestSeed}`;
  103. return new Promise<NativeResponse<T>>((resolve, reject) => {
  104. const timer = setTimeout(() => {
  105. this._pendingRequests.delete(requestId);
  106. reject(new Error(`Google ${method} 请求超时。`));
  107. }, timeoutMs);
  108. this._pendingRequests.set(requestId, {
  109. method,
  110. resolve,
  111. reject,
  112. timer,
  113. });
  114. try {
  115. native.jsbBridgeWrapper.dispatchEventToNative(
  116. REQUEST_EVENT,
  117. JSON.stringify({ requestId, method, params }),
  118. );
  119. } catch (error) {
  120. clearTimeout(timer);
  121. this._pendingRequests.delete(requestId);
  122. reject(error instanceof Error ? error : new Error(String(error)));
  123. }
  124. });
  125. }
  126. private onNativeResponse(payload: string): void {
  127. let response: NativeResponse<any>;
  128. try {
  129. response = JSON.parse(payload) as NativeResponse<any>;
  130. } catch (error) {
  131. console.error("[Google][Android] 无法解析原生响应:", error);
  132. return;
  133. }
  134. const pending = this._pendingRequests.get(response.requestId);
  135. if (!pending) return;
  136. clearTimeout(pending.timer);
  137. this._pendingRequests.delete(response.requestId);
  138. if (response.success) {
  139. pending.resolve(response);
  140. return;
  141. }
  142. const code = response.error?.code ? `[${response.error.code}] ` : "";
  143. pending.reject(new Error(`${code}${response.error?.message || `${pending.method} 执行失败。`}`));
  144. }
  145. private loginFailure(reason: string): IGoogleLoginRes {
  146. return {
  147. accessToken: "",
  148. isCancel: false,
  149. status: "error",
  150. reason,
  151. };
  152. }
  153. private errorMessage(error: unknown): string {
  154. return error instanceof Error ? error.message : String(error);
  155. }
  156. }