| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- import { native } from "cc";
- import {
- FacebookImpl,
- IFBEventParams,
- IFBLoginRes,
- IFBShareOptions,
- IFBShareRes,
- } from "./FacebookImpl";
- const REQUEST_EVENT = "facebook.request";
- const RESPONSE_EVENT = "facebook.response";
- const DEFAULT_TIMEOUT_MS = 15_000;
- const USER_OPERATION_TIMEOUT_MS = 180_000;
- interface NativeError {
- code?: string;
- message?: string;
- }
- interface NativeResponse<T = Record<string, never>> {
- requestId: string;
- method: string;
- success: boolean;
- cancelled?: boolean;
- data?: T;
- error?: NativeError;
- }
- interface LoginData {
- accessToken?: string;
- userId?: string;
- name?: string;
- authenticationToken?: string;
- nonce?: string;
- }
- interface ShareData {
- postId?: string;
- }
- interface EventData {
- accepted?: boolean;
- }
- interface PendingRequest {
- method: string;
- resolve: (response: NativeResponse<any>) => void;
- reject: (error: Error) => void;
- timer: ReturnType<typeof setTimeout>;
- }
- export class FBNativeImpl extends FacebookImpl {
- private _requestSeed = 0;
- private _pendingRequests = new Map<string, PendingRequest>();
- private _initPromise: Promise<boolean> | null = null;
- private readonly _responseListener = (payload: string) => this.onNativeResponse(payload);
- init(): void {
- if (this._initPromise) return;
- native.jsbBridgeWrapper.addNativeEventListener(RESPONSE_EVENT, this._responseListener);
- this._initPromise = this.request("init")
- .then(() => {
- this.onInited();
- return true;
- })
- .catch((error) => {
- console.error("[Facebook][Native] 初始化失败:", error);
- return false;
- });
- }
- protected async onLogin(): Promise<IFBLoginRes> {
- if (!(await this.ensureInitialized())) {
- return this.loginFailure("Facebook 原生 SDK 尚未初始化。");
- }
- try {
- const response = await this.request<LoginData>(
- "login",
- { permissions: ["public_profile", "email"] },
- USER_OPERATION_TIMEOUT_MS,
- );
- if (response.cancelled) {
- return {
- accessToken: "",
- isCancel: true,
- status: "cancelled",
- };
- }
- const accessToken = response.data?.accessToken || "";
- const authenticationToken = response.data?.authenticationToken || "";
- if (!accessToken && !authenticationToken) {
- return this.loginFailure("Facebook 登录未返回有效令牌。");
- }
- return {
- accessToken,
- userId: response.data?.userId,
- name: response.data?.name,
- ...(authenticationToken ? {
- authenticationToken,
- nonce: response.data?.nonce,
- tokenType: "authentication" as const,
- } : { tokenType: "access" as const }),
- isCancel: false,
- status: "connected",
- };
- } catch (error) {
- return this.loginFailure(this.errorMessage(error));
- }
- }
- async logOut(): Promise<boolean> {
- if (!(await this.ensureInitialized())) return false;
- try {
- await this.request("logout");
- return true;
- } catch (error) {
- console.error("[Facebook][Native] 登出失败:", error);
- return false;
- }
- }
- async share(options: IFBShareOptions): Promise<IFBShareRes> {
- if (!(await this.ensureInitialized())) {
- return {
- success: false,
- isCancel: false,
- reason: "Facebook 原生 SDK 尚未初始化。",
- };
- }
- try {
- const response = await this.request<ShareData>(
- "share",
- {
- url: options?.url || "",
- hashtag: options?.hashtag || "",
- quote: options?.quote || "",
- },
- USER_OPERATION_TIMEOUT_MS,
- );
- return {
- success: !response.cancelled,
- isCancel: Boolean(response.cancelled),
- postId: response.data?.postId,
- };
- } catch (error) {
- return {
- success: false,
- isCancel: false,
- reason: this.errorMessage(error),
- };
- }
- }
- async logEvent(
- eventName: string,
- parameters: IFBEventParams = {},
- valueToSum?: number,
- ): Promise<boolean> {
- if (!(await this.ensureInitialized())) return false;
- try {
- const params: Record<string, unknown> = { eventName, parameters };
- if (valueToSum !== undefined) params.valueToSum = valueToSum;
- const response = await this.request<EventData>("logEvent", params);
- return response.data?.accepted === true;
- } catch (error) {
- console.error(`[Facebook][Native] 事件 ${eventName} 记录失败:`, error);
- return false;
- }
- }
- async logPurchase(
- amount: number,
- currency: string,
- parameters: IFBEventParams = {},
- ): Promise<boolean> {
- if (!(await this.ensureInitialized())) return false;
- try {
- const response = await this.request<EventData>("logPurchase", {
- amount,
- currency,
- parameters,
- });
- return response.data?.accepted === true;
- } catch (error) {
- console.error("[Facebook][Native] 购买事件记录失败:", error);
- return false;
- }
- }
- async flushEvents(): Promise<boolean> {
- if (!(await this.ensureInitialized())) return false;
- try {
- const response = await this.request<EventData>("flushEvents");
- return response.data?.accepted === true;
- } catch (error) {
- console.error("[Facebook][Native] App Events 刷新失败:", error);
- return false;
- }
- }
- private async ensureInitialized(): Promise<boolean> {
- if (!this._initPromise) this.init();
- return this._initPromise!;
- }
- private request<T = Record<string, never>>(
- method: string,
- params: Record<string, unknown> = {},
- timeoutMs = DEFAULT_TIMEOUT_MS,
- ): Promise<NativeResponse<T>> {
- const requestId = `${Date.now()}-${++this._requestSeed}`;
- return new Promise<NativeResponse<T>>((resolve, reject) => {
- const timer = setTimeout(() => {
- this._pendingRequests.delete(requestId);
- reject(new Error(`Facebook ${method} 请求超时。`));
- }, timeoutMs);
- this._pendingRequests.set(requestId, {
- method,
- resolve,
- reject,
- timer,
- });
- try {
- native.jsbBridgeWrapper.dispatchEventToNative(
- REQUEST_EVENT,
- JSON.stringify({ requestId, method, params }),
- );
- } catch (error) {
- clearTimeout(timer);
- this._pendingRequests.delete(requestId);
- reject(error instanceof Error ? error : new Error(String(error)));
- }
- });
- }
- private onNativeResponse(payload: string): void {
- let response: NativeResponse<any>;
- try {
- response = JSON.parse(payload) as NativeResponse<any>;
- } catch (error) {
- console.error("[Facebook][Native] 无法解析原生响应:", error);
- return;
- }
- const pending = this._pendingRequests.get(response.requestId);
- if (!pending) return;
- clearTimeout(pending.timer);
- this._pendingRequests.delete(response.requestId);
- if (response.success) {
- pending.resolve(response);
- return;
- }
- const code = response.error?.code ? `[${response.error.code}] ` : "";
- pending.reject(new Error(`${code}${response.error?.message || `${pending.method} 执行失败。`}`));
- }
- private loginFailure(reason: string): IFBLoginRes {
- return {
- accessToken: "",
- isCancel: false,
- status: "error",
- reason,
- };
- }
- private errorMessage(error: unknown): string {
- return error instanceof Error ? error.message : String(error);
- }
- }
|