FBAndroidImpl.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { native } from "cc";
  2. import {
  3. FacebookImpl,
  4. IFBEventParams,
  5. IFBLoginRes,
  6. IFBShareOptions,
  7. IFBShareRes,
  8. } from "./FacebookImpl";
  9. const REQUEST_EVENT = "facebook.request";
  10. const RESPONSE_EVENT = "facebook.response";
  11. const DEFAULT_TIMEOUT_MS = 15_000;
  12. const USER_OPERATION_TIMEOUT_MS = 180_000;
  13. interface NativeError {
  14. code?: string;
  15. message?: string;
  16. }
  17. interface NativeResponse<T = Record<string, never>> {
  18. requestId: string;
  19. method: string;
  20. success: boolean;
  21. cancelled?: boolean;
  22. data?: T;
  23. error?: NativeError;
  24. }
  25. interface LoginData {
  26. accessToken?: string;
  27. userId?: string;
  28. }
  29. interface ShareData {
  30. postId?: string;
  31. }
  32. interface EventData {
  33. accepted?: boolean;
  34. }
  35. interface PendingRequest {
  36. method: string;
  37. resolve: (response: NativeResponse<any>) => void;
  38. reject: (error: Error) => void;
  39. timer: ReturnType<typeof setTimeout>;
  40. }
  41. export class FBAndroidImpl extends FacebookImpl {
  42. private _requestSeed = 0;
  43. private _pendingRequests = new Map<string, PendingRequest>();
  44. private _initPromise: Promise<boolean> | null = null;
  45. private readonly _responseListener = (payload: string) => this.onNativeResponse(payload);
  46. init(): void {
  47. if (this._initPromise) return;
  48. native.jsbBridgeWrapper.addNativeEventListener(RESPONSE_EVENT, this._responseListener);
  49. this._initPromise = this.request("init")
  50. .then(() => {
  51. this.onInited();
  52. return true;
  53. })
  54. .catch((error) => {
  55. console.error("[Facebook][Android] 初始化失败:", error);
  56. return false;
  57. });
  58. }
  59. protected async onLogin(): Promise<IFBLoginRes> {
  60. if (!(await this.ensureInitialized())) {
  61. return this.loginFailure("Facebook Android SDK 尚未初始化。");
  62. }
  63. try {
  64. const response = await this.request<LoginData>(
  65. "login",
  66. { permissions: ["public_profile", "email"] },
  67. USER_OPERATION_TIMEOUT_MS,
  68. );
  69. if (response.cancelled) {
  70. return {
  71. accessToken: "",
  72. isCancel: true,
  73. status: "cancelled",
  74. };
  75. }
  76. const accessToken = response.data?.accessToken || "";
  77. if (!accessToken) {
  78. return this.loginFailure("Facebook 登录未返回 Access Token。");
  79. }
  80. return {
  81. accessToken,
  82. isCancel: false,
  83. status: "connected",
  84. };
  85. } catch (error) {
  86. return this.loginFailure(this.errorMessage(error));
  87. }
  88. }
  89. async logOut(): Promise<boolean> {
  90. if (!(await this.ensureInitialized())) return false;
  91. try {
  92. await this.request("logout");
  93. return true;
  94. } catch (error) {
  95. console.error("[Facebook][Android] 登出失败:", error);
  96. return false;
  97. }
  98. }
  99. async share(options: IFBShareOptions): Promise<IFBShareRes> {
  100. if (!(await this.ensureInitialized())) {
  101. return {
  102. success: false,
  103. isCancel: false,
  104. reason: "Facebook Android SDK 尚未初始化。",
  105. };
  106. }
  107. try {
  108. const response = await this.request<ShareData>(
  109. "share",
  110. {
  111. url: options?.url || "",
  112. hashtag: options?.hashtag || "",
  113. quote: options?.quote || "",
  114. },
  115. USER_OPERATION_TIMEOUT_MS,
  116. );
  117. return {
  118. success: !response.cancelled,
  119. isCancel: Boolean(response.cancelled),
  120. postId: response.data?.postId,
  121. };
  122. } catch (error) {
  123. return {
  124. success: false,
  125. isCancel: false,
  126. reason: this.errorMessage(error),
  127. };
  128. }
  129. }
  130. async logEvent(
  131. eventName: string,
  132. parameters: IFBEventParams = {},
  133. valueToSum?: number,
  134. ): Promise<boolean> {
  135. if (!(await this.ensureInitialized())) return false;
  136. try {
  137. const params: Record<string, unknown> = { eventName, parameters };
  138. if (valueToSum !== undefined) params.valueToSum = valueToSum;
  139. const response = await this.request<EventData>("logEvent", params);
  140. return response.data?.accepted === true;
  141. } catch (error) {
  142. console.error(`[Facebook][Android] 事件 ${eventName} 记录失败:`, error);
  143. return false;
  144. }
  145. }
  146. async logPurchase(
  147. amount: number,
  148. currency: string,
  149. parameters: IFBEventParams = {},
  150. ): Promise<boolean> {
  151. if (!(await this.ensureInitialized())) return false;
  152. try {
  153. const response = await this.request<EventData>("logPurchase", {
  154. amount,
  155. currency,
  156. parameters,
  157. });
  158. return response.data?.accepted === true;
  159. } catch (error) {
  160. console.error("[Facebook][Android] 购买事件记录失败:", error);
  161. return false;
  162. }
  163. }
  164. async flushEvents(): Promise<boolean> {
  165. if (!(await this.ensureInitialized())) return false;
  166. try {
  167. const response = await this.request<EventData>("flushEvents");
  168. return response.data?.accepted === true;
  169. } catch (error) {
  170. console.error("[Facebook][Android] App Events 刷新失败:", error);
  171. return false;
  172. }
  173. }
  174. private async ensureInitialized(): Promise<boolean> {
  175. if (!this._initPromise) this.init();
  176. return this._initPromise!;
  177. }
  178. private request<T = Record<string, never>>(
  179. method: string,
  180. params: Record<string, unknown> = {},
  181. timeoutMs = DEFAULT_TIMEOUT_MS,
  182. ): Promise<NativeResponse<T>> {
  183. const requestId = `${Date.now()}-${++this._requestSeed}`;
  184. return new Promise<NativeResponse<T>>((resolve, reject) => {
  185. const timer = setTimeout(() => {
  186. this._pendingRequests.delete(requestId);
  187. reject(new Error(`Facebook ${method} 请求超时。`));
  188. }, timeoutMs);
  189. this._pendingRequests.set(requestId, {
  190. method,
  191. resolve,
  192. reject,
  193. timer,
  194. });
  195. try {
  196. native.jsbBridgeWrapper.dispatchEventToNative(
  197. REQUEST_EVENT,
  198. JSON.stringify({ requestId, method, params }),
  199. );
  200. } catch (error) {
  201. clearTimeout(timer);
  202. this._pendingRequests.delete(requestId);
  203. reject(error instanceof Error ? error : new Error(String(error)));
  204. }
  205. });
  206. }
  207. private onNativeResponse(payload: string): void {
  208. let response: NativeResponse<any>;
  209. try {
  210. response = JSON.parse(payload) as NativeResponse<any>;
  211. } catch (error) {
  212. console.error("[Facebook][Android] 无法解析原生响应:", error);
  213. return;
  214. }
  215. const pending = this._pendingRequests.get(response.requestId);
  216. if (!pending) return;
  217. clearTimeout(pending.timer);
  218. this._pendingRequests.delete(response.requestId);
  219. if (response.success) {
  220. pending.resolve(response);
  221. return;
  222. }
  223. const code = response.error?.code ? `[${response.error.code}] ` : "";
  224. pending.reject(new Error(`${code}${response.error?.message || `${pending.method} 执行失败。`}`));
  225. }
  226. private loginFailure(reason: string): IFBLoginRes {
  227. return {
  228. accessToken: "",
  229. isCancel: false,
  230. status: "error",
  231. reason,
  232. };
  233. }
  234. private errorMessage(error: unknown): string {
  235. return error instanceof Error ? error.message : String(error);
  236. }
  237. }