// Google Identity Services (GIS) 配置 // const GOOGLE_CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID! const GOOGLE_CLIENT_ID = "606396962663-9pagar3g9vuhovi37vq9jqob6q1gngns.apps.googleusercontent.com" // Google OAuth scopes const GOOGLE_SCOPES = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ].join(' ') export interface GoogleUser { id: string email: string verified_email: boolean name: string given_name: string family_name: string picture?: string locale?: string } // Google Identity Services 响应类型 export interface GoogleCredentialResponse { credential: string // JWT ID token select_by?: string clientId?: string } // Google OAuth Code Response (使用 Code Model) export interface GoogleCodeResponse { code: string // Authorization code scope: string authuser?: string prompt?: string } // 声明 Google Identity Services 全局对象 declare global { interface Window { google?: { accounts: { id: { initialize: (config: GoogleIdConfiguration) => void prompt: (momentListener?: (notification: PromptMomentNotification) => void) => void renderButton: (parent: HTMLElement, options: GsiButtonConfiguration) => void disableAutoSelect: () => void cancel: () => void } oauth2: { initCodeClient: (config: CodeClientConfig) => CodeClient initTokenClient: (config: TokenClientConfig) => TokenClient } } } } } interface GoogleIdConfiguration { client_id: string callback?: (response: GoogleCredentialResponse) => void auto_select?: boolean cancel_on_tap_outside?: boolean context?: 'signin' | 'signup' | 'use' ux_mode?: 'popup' | 'redirect' login_uri?: string native_callback?: (response: GoogleCredentialResponse) => void itp_support?: boolean } interface GsiButtonConfiguration { 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?: string locale?: string } interface PromptMomentNotification { isDisplayMoment: () => boolean isDisplayed: () => boolean isNotDisplayed: () => boolean getNotDisplayedReason: () => string isSkippedMoment: () => boolean getSkippedReason: () => string isDismissedMoment: () => boolean getDismissedReason: () => string getMomentType: () => string } interface CodeClientConfig { client_id: string scope: string callback: (response: GoogleCodeResponse) => void error_callback?: (error: { type: string; message: string }) => void ux_mode?: 'popup' | 'redirect' redirect_uri?: string state?: string } interface CodeClient { requestCode: () => void } interface TokenClientConfig { client_id: string scope: string callback: (response: { access_token: string; expires_in: number; scope: string; token_type: string }) => void error_callback?: (error: { type: string; message: string }) => void } interface TokenClient { requestAccessToken: () => void } export const googleOAuth = { clientId: GOOGLE_CLIENT_ID, scopes: GOOGLE_SCOPES, // 加载 Google Identity Services SDK loadScript: (): Promise => { return new Promise((resolve, reject) => { // 检查是否已加载 if (window.google?.accounts) { resolve() return } // 创建 script 标签 const script = document.createElement('script') script.src = 'https://accounts.google.com/gsi/client' script.async = true script.defer = true script.onload = () => resolve() script.onerror = () => reject(new Error('Failed to load Google Identity Services SDK')) document.head.appendChild(script) }) }, // 初始化 Code Client(推荐方式,获取授权码) initCodeClient: (callback: (response: GoogleCodeResponse) => void, errorCallback?: (error: any) => void) => { if (!window.google?.accounts?.oauth2) { throw new Error('Google Identity Services SDK not loaded') } return window.google.accounts.oauth2.initCodeClient({ client_id: GOOGLE_CLIENT_ID, scope: GOOGLE_SCOPES, ux_mode: 'popup', // 使用 popup 模式,不需要 redirect_uri callback, error_callback: errorCallback }) }, // 初始化 Token Client(直接获取 access token) initTokenClient: (callback: (response: { access_token: string; expires_in: number; scope: string; token_type: string }) => void, errorCallback?: (error: any) => void) => { if (!window.google?.accounts?.oauth2) { throw new Error('Google Identity Services SDK not loaded') } return window.google.accounts.oauth2.initTokenClient({ client_id: GOOGLE_CLIENT_ID, scope: GOOGLE_SCOPES, callback, error_callback: errorCallback }) }, // 初始化 Google Identity (获取 ID Token - JWT) initGoogleId: (callback: (response: GoogleCredentialResponse) => void) => { if (!window.google?.accounts?.id) { throw new Error('Google Identity Services SDK not loaded') } window.google.accounts.id.initialize({ client_id: GOOGLE_CLIENT_ID, callback, auto_select: false, cancel_on_tap_outside: true, ux_mode: 'popup' }) }, // 触发 One Tap 流程 promptOneTap: (callback: (response: GoogleCredentialResponse) => void) => { googleOAuth.initGoogleId(callback) if (window.google?.accounts?.id) { window.google.accounts.id.prompt((notification) => { if (notification.isNotDisplayed() || notification.isSkippedMoment()) { console.log('One Tap not displayed:', notification.getNotDisplayedReason() || notification.getSkippedReason()) } }) } }, // 使用 FedCM (Federated Credential Management) 方式获取 ID Token // 这种方式会弹出标准的 Google 登录窗口,无需用户预先登录 renderButton: (parent: HTMLElement, callback: (response: GoogleCredentialResponse) => void, options?: Partial) => { if (!window.google?.accounts?.id) { throw new Error('Google Identity Services SDK not loaded') } // 先初始化 window.google.accounts.id.initialize({ client_id: GOOGLE_CLIENT_ID, callback, auto_select: false, cancel_on_tap_outside: true }) // 渲染 Google 标准按钮 window.google.accounts.id.renderButton(parent, { type: 'standard', theme: 'outline', size: 'large', text: 'continue_with', shape: 'rectangular', logo_alignment: 'left', ...options }) } }