| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- import AppConfig from "@scripts/app/AppConfig";
- import { Camera, Canvas, Node, screen, UITransform, Vec3 } from "cc";
- import { GoogleImpl, IGoogleBtnLogin, IGoogleLoginRes } from "./GoogleImpl";
- interface GoogleCredentialResponse {
- credential?: string;
- select_by?: string;
- }
- interface GoogleIdentityService {
- initialize: (config: {
- client_id: string;
- callback: (response: GoogleCredentialResponse) => void;
- ux_mode?: "popup" | "redirect";
- }) => void;
- renderButton: (parent: HTMLElement, options: {
- type?: "standard" | "icon";
- theme?: "outline" | "filled_blue" | "filled_black";
- size?: "large" | "medium" | "small";
- text?: "signin_with" | "signup_with" | "continue_with" | "signin";
- shape?: "rectangular" | "pill" | "circle" | "square";
- logo_alignment?: "left" | "center";
- width?: number;
- }) => void;
- disableAutoSelect: () => void;
- }
- interface GoogleSdkWindow extends Window {
- google?: {
- accounts?: {
- id?: GoogleIdentityService;
- };
- };
- }
- const GOOGLE_SDK_ID = "google-identity-services";
- const GOOGLE_SDK_URL = "https://accounts.google.com/gsi/client";
- export class GoogleH5Impl extends GoogleImpl {
- private _initPromise: Promise<void> | null = null;
- private _loginCallback: (res: IGoogleLoginRes) => void;
- private _buttonSeed = 0;
- init(): Promise<void> {
- if (this.isInitialized) return Promise.resolve();
- if (this._initPromise) return this._initPromise;
- this._initPromise = this.loadGoogleSdk()
- .then(() => {
- const googleIdentity = this.getGoogleIdentity();
- if (!googleIdentity) {
- throw new Error("Google Identity Services 初始化失败");
- }
- googleIdentity.initialize({
- client_id: AppConfig.GoogleID,
- ux_mode: "popup",
- callback: response => this.handleCredentialResponse(response),
- });
- this.onInited();
- console.log(googleIdentity);
- })
- .catch(error => {
- this._initPromise = null;
- throw error;
- });
- return this._initPromise;
- }
- protected async onLogin(): Promise<IGoogleLoginRes> {
- return null;
- }
- async logOut(): Promise<boolean> {
- return true;
- }
- createLoginBtn(node: Node, callback: (res: IGoogleLoginRes) => void): IGoogleBtnLogin {
- const container = document.createElement("div");
- container.id = `google-login-button-${++this._buttonSeed}`;
- container.style.position = "fixed";
- container.style.zIndex = "9999";
- container.style.display = "flex";
- container.style.alignItems = "center";
- container.style.justifyContent = "center";
- container.style.overflow = "hidden";
- container.style.boxSizing = "border-box";
- container.style.opacity = "0";
- document.body.appendChild(container);
- let active = node.activeInHierarchy;
- let cleared = false;
- let animationFrameId = 0;
- this._loginCallback = callback;
- const clear = () => {
- if (cleared) return;
- cleared = true;
- window.cancelAnimationFrame(animationFrameId);
- container.remove();
- };
- const update = () => {
- if (cleared) return;
- if (!node.isValid) {
- clear();
- return;
- }
- this.syncDomLayout(container, node, active);
- animationFrameId = window.requestAnimationFrame(update);
- };
- this.syncDomLayout(container, node, active);
- animationFrameId = window.requestAnimationFrame(update);
- this.init()
- .then(() => {
- if (cleared || !node.isValid) return;
- this.syncDomLayout(container, node, active);
- const containerWidth = Math.round(container.getBoundingClientRect().width) || 280;
- const width = Math.max(40, Math.min(400, containerWidth));
- this.getGoogleIdentity()?.renderButton(container, {
- type: "standard",
- theme: "outline",
- size: "large",
- text: "signin_with",
- shape: "rectangular",
- logo_alignment: "left",
- width,
- });
- })
- .catch(error => {
- console.error("[Google] 登录按钮创建失败", error);
- clear();
- });
- return {
- active,
- setActive: (visible: boolean) => {
- if (cleared) return;
- if (!node.isValid) {
- clear();
- return;
- }
- active = visible;
- this.syncDomLayout(container, node, active);
- },
- clear,
- };
- }
- private handleCredentialResponse(response: GoogleCredentialResponse): void {
- console.log('[Google] handleCredentialResponse:', response, !!this._loginCallback)
- const credential = response?.credential || "";
- const result: IGoogleLoginRes = {
- // GIS 返回的是 JWT ID Token,沿用项目接口的 accessToken 字段传递。
- accessToken: credential,
- isCancel: !credential,
- status: credential ? "connected" : "failed",
- reason: credential ? undefined : "Google 登录未返回 credential",
- };
- this._loginCallback?.(result);
- }
- private getGoogleIdentity(): GoogleIdentityService | undefined {
- return (window as GoogleSdkWindow).google?.accounts?.id;
- }
- private loadGoogleSdk(): Promise<void> {
- if (this.getGoogleIdentity()) return Promise.resolve();
- return new Promise((resolve, reject) => {
- let script = document.getElementById(GOOGLE_SDK_ID) as HTMLScriptElement | null;
- if (!script) {
- script = document.createElement("script");
- script.id = GOOGLE_SDK_ID;
- script.src = GOOGLE_SDK_URL;
- script.async = true;
- script.defer = true;
- document.head.appendChild(script);
- }
- const clearListeners = () => {
- window.clearTimeout(timeoutId);
- script?.removeEventListener("load", onLoad);
- script?.removeEventListener("error", onError);
- };
- const onLoad = () => {
- clearListeners();
- this.getGoogleIdentity()
- ? resolve()
- : reject(new Error("Google Identity Services SDK 不可用"));
- };
- const onError = () => {
- clearListeners();
- script?.remove();
- reject(new Error("Google Identity Services SDK 下载失败"));
- };
- const timeoutId = window.setTimeout(() => {
- clearListeners();
- script?.remove();
- reject(new Error("Google Identity Services SDK 加载超时"));
- }, 15000);
- script.addEventListener("load", onLoad, { once: true });
- script.addEventListener("error", onError, { once: true });
- });
- }
- private syncDomLayout(container: HTMLDivElement, node: Node, active: boolean): void {
- if (!active || !node.activeInHierarchy) {
- container.style.display = "none";
- return;
- }
- const uiTransform = node.getComponent(UITransform);
- const camera = this.findCanvasCamera(node);
- const gameCanvas = this.getGameCanvas();
- if (!uiTransform || !camera || !gameCanvas) {
- container.style.display = "none";
- return;
- }
- const worldBounds = uiTransform.getBoundingBoxToWorld();
- const worldZ = node.worldPosition.z;
- const bottomLeft = camera.worldToScreen(new Vec3(worldBounds.xMin, worldBounds.yMin, worldZ));
- const topRight = camera.worldToScreen(new Vec3(worldBounds.xMax, worldBounds.yMax, worldZ));
- const screenWidth = screen.windowSize.width || gameCanvas.width;
- const screenHeight = screen.windowSize.height || gameCanvas.height;
- if (screenWidth <= 0 || screenHeight <= 0) {
- container.style.display = "none";
- return;
- }
- const canvasRect = gameCanvas.getBoundingClientRect();
- const minX = Math.min(bottomLeft.x, topRight.x);
- const maxX = Math.max(bottomLeft.x, topRight.x);
- const minY = Math.min(bottomLeft.y, topRight.y);
- const maxY = Math.max(bottomLeft.y, topRight.y);
- container.style.display = "flex";
- container.style.left = `${canvasRect.left + minX / screenWidth * canvasRect.width}px`;
- container.style.top = `${canvasRect.top + (1 - maxY / screenHeight) * canvasRect.height}px`;
- container.style.width = `${(maxX - minX) / screenWidth * canvasRect.width}px`;
- container.style.height = `${(maxY - minY) / screenHeight * canvasRect.height}px`;
- }
- private findCanvasCamera(node: Node): Camera | null {
- let current: Node | null = node;
- while (current) {
- const canvas = current.getComponent(Canvas);
- if (canvas) return canvas.cameraComponent || current.getComponent(Camera);
- current = current.parent;
- }
- return null;
- }
- private getGameCanvas(): HTMLCanvasElement | null {
- const gameCanvas = document.getElementById("GameCanvas");
- if (gameCanvas instanceof HTMLCanvasElement) return gameCanvas;
- return document.querySelector("canvas");
- }
- }
|