@seaverse/auth-sdk 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +362 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +235 -2
- package/dist/index.js +362 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1651,5 +1651,238 @@ declare class Toast {
|
|
|
1651
1651
|
private static injectCSS;
|
|
1652
1652
|
}
|
|
1653
1653
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1654
|
+
/**
|
|
1655
|
+
* Google One-Tap Sign-In SDK
|
|
1656
|
+
*
|
|
1657
|
+
* 用于在用户未登录 SeaVerse 但已登录 Google 的情况下,
|
|
1658
|
+
* 自动显示 Google One-Tap 快速登录界面
|
|
1659
|
+
*/
|
|
1660
|
+
declare global {
|
|
1661
|
+
interface Window {
|
|
1662
|
+
google?: {
|
|
1663
|
+
accounts: {
|
|
1664
|
+
id: {
|
|
1665
|
+
initialize(config: GoogleIdConfiguration): void;
|
|
1666
|
+
prompt(momentListener?: (notification: PromptMomentNotification) => void): void;
|
|
1667
|
+
cancel(): void;
|
|
1668
|
+
renderButton(parent: HTMLElement, options: GsiButtonConfiguration): void;
|
|
1669
|
+
disableAutoSelect(): void;
|
|
1670
|
+
};
|
|
1671
|
+
};
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
interface GoogleIdConfiguration {
|
|
1676
|
+
client_id: string;
|
|
1677
|
+
callback: (response: CredentialResponse) => void;
|
|
1678
|
+
auto_select?: boolean;
|
|
1679
|
+
cancel_on_tap_outside?: boolean;
|
|
1680
|
+
context?: 'signin' | 'signup' | 'use';
|
|
1681
|
+
use_fedcm_for_prompt?: boolean;
|
|
1682
|
+
}
|
|
1683
|
+
interface CredentialResponse {
|
|
1684
|
+
credential: string;
|
|
1685
|
+
select_by?: string;
|
|
1686
|
+
}
|
|
1687
|
+
interface PromptMomentNotification {
|
|
1688
|
+
isDisplayMoment(): boolean;
|
|
1689
|
+
isDisplayed(): boolean;
|
|
1690
|
+
isNotDisplayed(): boolean;
|
|
1691
|
+
getNotDisplayedReason(): string;
|
|
1692
|
+
isSkippedMoment(): boolean;
|
|
1693
|
+
getSkippedReason(): string;
|
|
1694
|
+
isDismissedMoment(): boolean;
|
|
1695
|
+
getDismissedReason(): string;
|
|
1696
|
+
}
|
|
1697
|
+
interface GsiButtonConfiguration {
|
|
1698
|
+
type?: 'standard' | 'icon';
|
|
1699
|
+
theme?: 'outline' | 'filled_blue' | 'filled_black';
|
|
1700
|
+
size?: 'large' | 'medium' | 'small';
|
|
1701
|
+
text?: 'signin_with' | 'signup_with' | 'continue_with' | 'signin';
|
|
1702
|
+
shape?: 'rectangular' | 'pill' | 'circle' | 'square';
|
|
1703
|
+
logo_alignment?: 'left' | 'center';
|
|
1704
|
+
width?: number;
|
|
1705
|
+
locale?: string;
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Google One-Tap 配置选项
|
|
1709
|
+
*/
|
|
1710
|
+
interface GoogleOneTapOptions {
|
|
1711
|
+
/**
|
|
1712
|
+
* Google OAuth Client ID(必需)
|
|
1713
|
+
* 从 Google Cloud Console 获取
|
|
1714
|
+
*
|
|
1715
|
+
* @example '827924048330-xxxxxx.apps.googleusercontent.com'
|
|
1716
|
+
*/
|
|
1717
|
+
clientId: string;
|
|
1718
|
+
/**
|
|
1719
|
+
* 检查用户是否已登录 SeaVerse(必需)
|
|
1720
|
+
* SDK 会在初始化时和显示 One-Tap 前调用此函数
|
|
1721
|
+
* 只有返回 false 时才会显示 Google One-Tap
|
|
1722
|
+
*
|
|
1723
|
+
* @returns true - 已登录,SDK 不显示 One-Tap
|
|
1724
|
+
* false - 未登录,SDK 显示 One-Tap
|
|
1725
|
+
*
|
|
1726
|
+
* @example
|
|
1727
|
+
* isLoggedIn: () => !!localStorage.getItem('seaverse_token')
|
|
1728
|
+
*
|
|
1729
|
+
* @example
|
|
1730
|
+
* isLoggedIn: () => document.cookie.includes('auth_token')
|
|
1731
|
+
*/
|
|
1732
|
+
isLoggedIn: () => boolean;
|
|
1733
|
+
/**
|
|
1734
|
+
* 获取到 Google credential 后的回调(必需)
|
|
1735
|
+
* SDK 获取到 Google 返回的 JWT credential 后会调用此函数
|
|
1736
|
+
* 前端在此函数中决定如何处理 credential(调用后端验证、保存 token 等)
|
|
1737
|
+
*
|
|
1738
|
+
* @param credential - Google 返回的 JWT token
|
|
1739
|
+
*
|
|
1740
|
+
* @example
|
|
1741
|
+
* onCredentialReceived: async (credential) => {
|
|
1742
|
+
* const res = await fetch('/api/auth/google/verify', {
|
|
1743
|
+
* method: 'POST',
|
|
1744
|
+
* body: JSON.stringify({ credential })
|
|
1745
|
+
* });
|
|
1746
|
+
* const data = await res.json();
|
|
1747
|
+
* localStorage.setItem('token', data.token);
|
|
1748
|
+
* window.location.href = '/dashboard';
|
|
1749
|
+
* }
|
|
1750
|
+
*/
|
|
1751
|
+
onCredentialReceived: (credential: string) => void | Promise<void>;
|
|
1752
|
+
/**
|
|
1753
|
+
* 错误回调(可选)
|
|
1754
|
+
*
|
|
1755
|
+
* @param error - 错误对象
|
|
1756
|
+
*/
|
|
1757
|
+
onError?: (error: Error) => void;
|
|
1758
|
+
/**
|
|
1759
|
+
* 是否自动显示 One-Tap 提示(可选,默认 true)
|
|
1760
|
+
* 注意:即使设置为 true,也只在 isLoggedIn() 返回 false 时才显示
|
|
1761
|
+
*/
|
|
1762
|
+
autoPrompt?: boolean;
|
|
1763
|
+
/**
|
|
1764
|
+
* 点击 One-Tap 弹窗外部时是否取消(可选,默认 true)
|
|
1765
|
+
*/
|
|
1766
|
+
cancelOnTapOutside?: boolean;
|
|
1767
|
+
/**
|
|
1768
|
+
* 登录上下文(可选,默认 'signin')
|
|
1769
|
+
*/
|
|
1770
|
+
context?: 'signin' | 'signup' | 'use';
|
|
1771
|
+
/**
|
|
1772
|
+
* 自定义按钮配置(可选)
|
|
1773
|
+
* 用于 renderButton() 方法
|
|
1774
|
+
*/
|
|
1775
|
+
button?: {
|
|
1776
|
+
type?: 'standard' | 'icon';
|
|
1777
|
+
theme?: 'outline' | 'filled_blue' | 'filled_black';
|
|
1778
|
+
size?: 'large' | 'medium' | 'small';
|
|
1779
|
+
text?: 'signin_with' | 'signup_with' | 'continue_with' | 'signin';
|
|
1780
|
+
shape?: 'rectangular' | 'pill' | 'circle' | 'square';
|
|
1781
|
+
logoAlignment?: 'left' | 'center';
|
|
1782
|
+
width?: number;
|
|
1783
|
+
locale?: string;
|
|
1784
|
+
};
|
|
1785
|
+
/**
|
|
1786
|
+
* 是否启用调试日志(可选,默认 false)
|
|
1787
|
+
*/
|
|
1788
|
+
debug?: boolean;
|
|
1789
|
+
/**
|
|
1790
|
+
* 是否使用 FedCM (Federated Credential Management) 进行提示(可选,默认 false)
|
|
1791
|
+
*
|
|
1792
|
+
* FedCM 是 Chrome 的新 API,用于替代第三方 Cookie。
|
|
1793
|
+
* 如果遇到 CORS 错误或 FedCM 请求失败,可以设置为 false 使用传统的 iframe 方式。
|
|
1794
|
+
*
|
|
1795
|
+
* @default false
|
|
1796
|
+
*
|
|
1797
|
+
* @example
|
|
1798
|
+
* useFedcmForPrompt: false // 禁用 FedCM,使用传统方式
|
|
1799
|
+
*/
|
|
1800
|
+
useFedcmForPrompt?: boolean;
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Google One-Tap 快速登录
|
|
1804
|
+
*
|
|
1805
|
+
* 用法示例:
|
|
1806
|
+
* ```typescript
|
|
1807
|
+
* const googleLogin = new GoogleOneTap({
|
|
1808
|
+
* clientId: 'YOUR_GOOGLE_CLIENT_ID',
|
|
1809
|
+
* isLoggedIn: () => !!localStorage.getItem('token'),
|
|
1810
|
+
* onCredentialReceived: async (credential) => {
|
|
1811
|
+
* // 调用后端验证
|
|
1812
|
+
* const res = await fetch('/api/auth/google', {
|
|
1813
|
+
* method: 'POST',
|
|
1814
|
+
* body: JSON.stringify({ credential })
|
|
1815
|
+
* });
|
|
1816
|
+
* const data = await res.json();
|
|
1817
|
+
* localStorage.setItem('token', data.token);
|
|
1818
|
+
* window.location.reload();
|
|
1819
|
+
* }
|
|
1820
|
+
* });
|
|
1821
|
+
* ```
|
|
1822
|
+
*/
|
|
1823
|
+
declare class GoogleOneTap {
|
|
1824
|
+
private options;
|
|
1825
|
+
private isInitialized;
|
|
1826
|
+
private scriptLoaded;
|
|
1827
|
+
private static scriptLoadPromise;
|
|
1828
|
+
constructor(options: GoogleOneTapOptions);
|
|
1829
|
+
/**
|
|
1830
|
+
* 检查登录状态并初始化
|
|
1831
|
+
*/
|
|
1832
|
+
private checkAndInit;
|
|
1833
|
+
/**
|
|
1834
|
+
* 初始化 Google One-Tap API
|
|
1835
|
+
*/
|
|
1836
|
+
private init;
|
|
1837
|
+
/**
|
|
1838
|
+
* 处理 Google 返回的 credential
|
|
1839
|
+
*/
|
|
1840
|
+
private handleCredentialResponse;
|
|
1841
|
+
/**
|
|
1842
|
+
* 显示 Google One-Tap 提示
|
|
1843
|
+
* 会先检查登录状态
|
|
1844
|
+
*/
|
|
1845
|
+
prompt(): void;
|
|
1846
|
+
/**
|
|
1847
|
+
* 取消 Google One-Tap 提示
|
|
1848
|
+
*/
|
|
1849
|
+
cancel(): void;
|
|
1850
|
+
/**
|
|
1851
|
+
* 渲染 Google 登录按钮到指定元素
|
|
1852
|
+
*
|
|
1853
|
+
* @param element - 要渲染按钮的 HTML 元素
|
|
1854
|
+
*
|
|
1855
|
+
* @example
|
|
1856
|
+
* const container = document.getElementById('google-btn');
|
|
1857
|
+
* googleLogin.renderButton(container);
|
|
1858
|
+
*/
|
|
1859
|
+
renderButton(element: HTMLElement): void;
|
|
1860
|
+
/**
|
|
1861
|
+
* 禁用自动选择
|
|
1862
|
+
* 用户关闭 One-Tap 后,下次不会自动选择账号
|
|
1863
|
+
*/
|
|
1864
|
+
disableAutoSelect(): void;
|
|
1865
|
+
/**
|
|
1866
|
+
* 等待 Google API 完全就绪
|
|
1867
|
+
*/
|
|
1868
|
+
private waitForGoogleAPI;
|
|
1869
|
+
/**
|
|
1870
|
+
* 加载 Google One-Tap 脚本
|
|
1871
|
+
*/
|
|
1872
|
+
private loadGoogleScript;
|
|
1873
|
+
/**
|
|
1874
|
+
* 处理错误
|
|
1875
|
+
*/
|
|
1876
|
+
private handleError;
|
|
1877
|
+
/**
|
|
1878
|
+
* 调试日志
|
|
1879
|
+
*/
|
|
1880
|
+
private log;
|
|
1881
|
+
/**
|
|
1882
|
+
* 检查 Google One-Tap 是否可用
|
|
1883
|
+
*/
|
|
1884
|
+
static isAvailable(): boolean;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
export { AuthFactory, AuthModal, AuthProvider, BuiltInHooks, ENVIRONMENT_CONFIGS, ErrorCode, GoogleOneTap, SeaVerseBackendAPIClient, Toast, createAuthModal, detectEnvironment, getEnvironmentConfig, models };
|
|
1888
|
+
export type { AccountExistsErrorDetails, ApiError, ApiServiceTokenResponse, ApplyInviteResponse, AuthModalOptions, AuthModalResult, BindInviteCodeRequest, BindInviteCodeResponse, Container, ContainerDetailResponse, ContainerListResponse, ContainerStatsResponse, ConversationStatus, CreateMarketplaceSkillRequest, CreateVideoShareRequest, CreateVideoShareResponse, EmailVerificationResponse, Environment, EnvironmentConfig, ForgotPasswordRequest, ForkProjectRequest, ForkProjectResponse, GoogleOneTapOptions, HealthResponse, HubProject, HubProjectsListResponse, IframeTokenErrorMessage, IframeTokenRequestMessage, IframeTokenResponseMessage, InviteApplication, InviteApplicationListResponse, InviteCode, InviteCodeBindRequest, InviteCodeDetailResponse, InviteCodeGenerateResponse, InviteCodeRequiredErrorData, InviteCodeVerifyResponse, InviteStats, InviteStatsResponse, InviteUsage, ListInviteUsagesRequest, ListInviteUsagesResponse, ListInvitesRequest, ListInvitesResponse, LoginRequest, LoginResponse, MarketplaceSkill, MarketplaceSkillsListResponse, OAuthAuthorizeRequest, OAuthAuthorizeResponse, PublishProjectRequest, PublishSkillRequest, RegisterContainerRequest, RegisterContainerResponse, RegisterRequest, RegisterResponse, ResetPasswordRequest, RetryOptions, SeaVerseBackendAPIClientOptions, SocialMediaLink, SocialMediaLinksResponse, SpeechTokenResponse, SubmitInviteApplicationRequest, SuccessResponse, ToastOptions, ToastType, TrackAppTypeRequest, User, UserInstalledSkill, UserInstalledSkillsListResponse, VideoDetails };
|
package/dist/index.js
CHANGED
|
@@ -4322,5 +4322,366 @@ function createAuthModal(options) {
|
|
|
4322
4322
|
return new AuthModal(options);
|
|
4323
4323
|
}
|
|
4324
4324
|
|
|
4325
|
-
|
|
4325
|
+
/**
|
|
4326
|
+
* Google One-Tap Sign-In SDK
|
|
4327
|
+
*
|
|
4328
|
+
* 用于在用户未登录 SeaVerse 但已登录 Google 的情况下,
|
|
4329
|
+
* 自动显示 Google One-Tap 快速登录界面
|
|
4330
|
+
*/
|
|
4331
|
+
/**
|
|
4332
|
+
* Google One-Tap 快速登录
|
|
4333
|
+
*
|
|
4334
|
+
* 用法示例:
|
|
4335
|
+
* ```typescript
|
|
4336
|
+
* const googleLogin = new GoogleOneTap({
|
|
4337
|
+
* clientId: 'YOUR_GOOGLE_CLIENT_ID',
|
|
4338
|
+
* isLoggedIn: () => !!localStorage.getItem('token'),
|
|
4339
|
+
* onCredentialReceived: async (credential) => {
|
|
4340
|
+
* // 调用后端验证
|
|
4341
|
+
* const res = await fetch('/api/auth/google', {
|
|
4342
|
+
* method: 'POST',
|
|
4343
|
+
* body: JSON.stringify({ credential })
|
|
4344
|
+
* });
|
|
4345
|
+
* const data = await res.json();
|
|
4346
|
+
* localStorage.setItem('token', data.token);
|
|
4347
|
+
* window.location.reload();
|
|
4348
|
+
* }
|
|
4349
|
+
* });
|
|
4350
|
+
* ```
|
|
4351
|
+
*/
|
|
4352
|
+
class GoogleOneTap {
|
|
4353
|
+
constructor(options) {
|
|
4354
|
+
this.isInitialized = false;
|
|
4355
|
+
this.scriptLoaded = false;
|
|
4356
|
+
this.options = options;
|
|
4357
|
+
this.log('GoogleOneTap initialized with options:', options);
|
|
4358
|
+
// 立即检查登录状态并初始化
|
|
4359
|
+
this.checkAndInit();
|
|
4360
|
+
}
|
|
4361
|
+
/**
|
|
4362
|
+
* 检查登录状态并初始化
|
|
4363
|
+
*/
|
|
4364
|
+
checkAndInit() {
|
|
4365
|
+
// 检查用户是否已登录 SeaVerse
|
|
4366
|
+
if (this.options.isLoggedIn()) {
|
|
4367
|
+
this.log('User already logged in, skipping Google One-Tap initialization');
|
|
4368
|
+
return;
|
|
4369
|
+
}
|
|
4370
|
+
this.log('User not logged in, initializing Google One-Tap');
|
|
4371
|
+
// 未登录,加载并初始化 Google One-Tap
|
|
4372
|
+
this.loadGoogleScript()
|
|
4373
|
+
.then(() => {
|
|
4374
|
+
this.init();
|
|
4375
|
+
})
|
|
4376
|
+
.catch((error) => {
|
|
4377
|
+
this.handleError(new Error(`Failed to load Google One-Tap script: ${error.message}`));
|
|
4378
|
+
});
|
|
4379
|
+
}
|
|
4380
|
+
/**
|
|
4381
|
+
* 初始化 Google One-Tap API
|
|
4382
|
+
*/
|
|
4383
|
+
init() {
|
|
4384
|
+
if (!window.google?.accounts?.id) {
|
|
4385
|
+
this.handleError(new Error('Google One-Tap API not available'));
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
try {
|
|
4389
|
+
// 初始化 Google One-Tap
|
|
4390
|
+
window.google.accounts.id.initialize({
|
|
4391
|
+
client_id: this.options.clientId,
|
|
4392
|
+
callback: (response) => {
|
|
4393
|
+
this.handleCredentialResponse(response);
|
|
4394
|
+
},
|
|
4395
|
+
auto_select: false,
|
|
4396
|
+
cancel_on_tap_outside: this.options.cancelOnTapOutside ?? true,
|
|
4397
|
+
context: this.options.context || 'signin',
|
|
4398
|
+
use_fedcm_for_prompt: this.options.useFedcmForPrompt ?? false
|
|
4399
|
+
});
|
|
4400
|
+
this.isInitialized = true;
|
|
4401
|
+
this.log('Google One-Tap initialized successfully');
|
|
4402
|
+
// 如果启用自动提示,显示 One-Tap
|
|
4403
|
+
if (this.options.autoPrompt !== false) {
|
|
4404
|
+
this.prompt();
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
catch (error) {
|
|
4408
|
+
this.handleError(new Error(`Failed to initialize Google One-Tap: ${error.message}`));
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
/**
|
|
4412
|
+
* 处理 Google 返回的 credential
|
|
4413
|
+
*/
|
|
4414
|
+
async handleCredentialResponse(response) {
|
|
4415
|
+
this.log('Received credential from Google', {
|
|
4416
|
+
select_by: response.select_by,
|
|
4417
|
+
credential_length: response.credential?.length
|
|
4418
|
+
});
|
|
4419
|
+
// 验证 credential 是否存在
|
|
4420
|
+
if (!response.credential) {
|
|
4421
|
+
const error = new Error('No credential received from Google');
|
|
4422
|
+
this.log('Error: No credential in response', response);
|
|
4423
|
+
this.handleError(error);
|
|
4424
|
+
return;
|
|
4425
|
+
}
|
|
4426
|
+
try {
|
|
4427
|
+
await this.options.onCredentialReceived(response.credential);
|
|
4428
|
+
this.log('Credential processed successfully');
|
|
4429
|
+
}
|
|
4430
|
+
catch (error) {
|
|
4431
|
+
this.log('Error processing credential:', error);
|
|
4432
|
+
this.handleError(error);
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
/**
|
|
4436
|
+
* 显示 Google One-Tap 提示
|
|
4437
|
+
* 会先检查登录状态
|
|
4438
|
+
*/
|
|
4439
|
+
prompt() {
|
|
4440
|
+
// 再次检查登录状态(可能在初始化后用户已登录)
|
|
4441
|
+
if (this.options.isLoggedIn()) {
|
|
4442
|
+
this.log('User already logged in, skipping prompt');
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
if (!this.isInitialized) {
|
|
4446
|
+
this.log('Google One-Tap not initialized yet, skipping prompt');
|
|
4447
|
+
return;
|
|
4448
|
+
}
|
|
4449
|
+
if (!window.google?.accounts?.id) {
|
|
4450
|
+
this.handleError(new Error('Google One-Tap API not available'));
|
|
4451
|
+
return;
|
|
4452
|
+
}
|
|
4453
|
+
try {
|
|
4454
|
+
this.log('Displaying Google One-Tap prompt');
|
|
4455
|
+
window.google.accounts.id.prompt((notification) => {
|
|
4456
|
+
if (notification.isNotDisplayed()) {
|
|
4457
|
+
const reason = notification.getNotDisplayedReason();
|
|
4458
|
+
this.log('One-Tap not displayed:', reason);
|
|
4459
|
+
}
|
|
4460
|
+
else if (notification.isSkippedMoment()) {
|
|
4461
|
+
const reason = notification.getSkippedReason();
|
|
4462
|
+
this.log('One-Tap skipped:', reason);
|
|
4463
|
+
}
|
|
4464
|
+
else if (notification.isDismissedMoment()) {
|
|
4465
|
+
const reason = notification.getDismissedReason();
|
|
4466
|
+
this.log('One-Tap dismissed:', reason);
|
|
4467
|
+
}
|
|
4468
|
+
else if (notification.isDisplayed()) {
|
|
4469
|
+
this.log('One-Tap displayed successfully');
|
|
4470
|
+
}
|
|
4471
|
+
});
|
|
4472
|
+
}
|
|
4473
|
+
catch (error) {
|
|
4474
|
+
this.handleError(new Error(`Failed to show Google One-Tap: ${error.message}`));
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
/**
|
|
4478
|
+
* 取消 Google One-Tap 提示
|
|
4479
|
+
*/
|
|
4480
|
+
cancel() {
|
|
4481
|
+
if (!this.isInitialized) {
|
|
4482
|
+
this.log('Google One-Tap not initialized, nothing to cancel');
|
|
4483
|
+
return;
|
|
4484
|
+
}
|
|
4485
|
+
if (!window.google?.accounts?.id) {
|
|
4486
|
+
return;
|
|
4487
|
+
}
|
|
4488
|
+
try {
|
|
4489
|
+
window.google.accounts.id.cancel();
|
|
4490
|
+
this.log('Google One-Tap canceled');
|
|
4491
|
+
}
|
|
4492
|
+
catch (error) {
|
|
4493
|
+
this.log('Error canceling Google One-Tap:', error);
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
/**
|
|
4497
|
+
* 渲染 Google 登录按钮到指定元素
|
|
4498
|
+
*
|
|
4499
|
+
* @param element - 要渲染按钮的 HTML 元素
|
|
4500
|
+
*
|
|
4501
|
+
* @example
|
|
4502
|
+
* const container = document.getElementById('google-btn');
|
|
4503
|
+
* googleLogin.renderButton(container);
|
|
4504
|
+
*/
|
|
4505
|
+
renderButton(element) {
|
|
4506
|
+
// 检查登录状态
|
|
4507
|
+
if (this.options.isLoggedIn()) {
|
|
4508
|
+
this.log('User already logged in, not rendering button');
|
|
4509
|
+
return;
|
|
4510
|
+
}
|
|
4511
|
+
if (!this.isInitialized) {
|
|
4512
|
+
this.log('Google One-Tap not initialized yet, waiting...');
|
|
4513
|
+
// 等待初始化完成后再渲染
|
|
4514
|
+
setTimeout(() => this.renderButton(element), 100);
|
|
4515
|
+
return;
|
|
4516
|
+
}
|
|
4517
|
+
if (!window.google?.accounts?.id) {
|
|
4518
|
+
this.handleError(new Error('Google One-Tap API not available'));
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4521
|
+
try {
|
|
4522
|
+
const buttonConfig = {
|
|
4523
|
+
type: this.options.button?.type || 'standard',
|
|
4524
|
+
theme: this.options.button?.theme || 'outline',
|
|
4525
|
+
size: this.options.button?.size || 'large',
|
|
4526
|
+
text: this.options.button?.text || 'signin_with',
|
|
4527
|
+
shape: this.options.button?.shape || 'rectangular',
|
|
4528
|
+
logo_alignment: this.options.button?.logoAlignment || 'left',
|
|
4529
|
+
width: this.options.button?.width,
|
|
4530
|
+
locale: this.options.button?.locale
|
|
4531
|
+
};
|
|
4532
|
+
window.google.accounts.id.renderButton(element, buttonConfig);
|
|
4533
|
+
this.log('Google login button rendered', buttonConfig);
|
|
4534
|
+
}
|
|
4535
|
+
catch (error) {
|
|
4536
|
+
this.handleError(new Error(`Failed to render Google button: ${error.message}`));
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
/**
|
|
4540
|
+
* 禁用自动选择
|
|
4541
|
+
* 用户关闭 One-Tap 后,下次不会自动选择账号
|
|
4542
|
+
*/
|
|
4543
|
+
disableAutoSelect() {
|
|
4544
|
+
if (!window.google?.accounts?.id) {
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
4547
|
+
try {
|
|
4548
|
+
window.google.accounts.id.disableAutoSelect();
|
|
4549
|
+
this.log('Auto select disabled');
|
|
4550
|
+
}
|
|
4551
|
+
catch (error) {
|
|
4552
|
+
this.log('Error disabling auto select:', error);
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
/**
|
|
4556
|
+
* 等待 Google API 完全就绪
|
|
4557
|
+
*/
|
|
4558
|
+
waitForGoogleAPI(maxAttempts = 50, interval = 100) {
|
|
4559
|
+
return new Promise((resolve, reject) => {
|
|
4560
|
+
let attempts = 0;
|
|
4561
|
+
const checkAPI = () => {
|
|
4562
|
+
if (window.google?.accounts?.id) {
|
|
4563
|
+
resolve();
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
attempts++;
|
|
4567
|
+
if (attempts >= maxAttempts) {
|
|
4568
|
+
reject(new Error('Google One-Tap API not available after timeout'));
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4571
|
+
setTimeout(checkAPI, interval);
|
|
4572
|
+
};
|
|
4573
|
+
checkAPI();
|
|
4574
|
+
});
|
|
4575
|
+
}
|
|
4576
|
+
/**
|
|
4577
|
+
* 加载 Google One-Tap 脚本
|
|
4578
|
+
*/
|
|
4579
|
+
loadGoogleScript() {
|
|
4580
|
+
// 如果已经有加载中的 Promise,直接返回
|
|
4581
|
+
if (GoogleOneTap.scriptLoadPromise) {
|
|
4582
|
+
return GoogleOneTap.scriptLoadPromise;
|
|
4583
|
+
}
|
|
4584
|
+
// 如果脚本已加载完成且 API 可用,创建已 resolve 的 Promise
|
|
4585
|
+
if (this.scriptLoaded && window.google?.accounts?.id) {
|
|
4586
|
+
return Promise.resolve();
|
|
4587
|
+
}
|
|
4588
|
+
// 检查脚本标签是否已存在
|
|
4589
|
+
const existingScript = document.getElementById('google-one-tap-script');
|
|
4590
|
+
if (existingScript) {
|
|
4591
|
+
// 如果脚本标签存在,等待其加载完成
|
|
4592
|
+
GoogleOneTap.scriptLoadPromise = new Promise((resolve, reject) => {
|
|
4593
|
+
if (window.google?.accounts?.id) {
|
|
4594
|
+
this.scriptLoaded = true;
|
|
4595
|
+
resolve();
|
|
4596
|
+
return;
|
|
4597
|
+
}
|
|
4598
|
+
// 等待脚本加载
|
|
4599
|
+
const onLoad = () => {
|
|
4600
|
+
// 等待 Google API 就绪
|
|
4601
|
+
this.waitForGoogleAPI()
|
|
4602
|
+
.then(() => {
|
|
4603
|
+
this.scriptLoaded = true;
|
|
4604
|
+
resolve();
|
|
4605
|
+
})
|
|
4606
|
+
.catch(reject);
|
|
4607
|
+
};
|
|
4608
|
+
// 检查脚本是否已经加载完成(通过检查 API 是否可用)
|
|
4609
|
+
// 如果 API 已经可用,直接调用 onLoad
|
|
4610
|
+
if (window.google?.accounts?.id) {
|
|
4611
|
+
onLoad();
|
|
4612
|
+
}
|
|
4613
|
+
else {
|
|
4614
|
+
// 否则等待脚本加载事件
|
|
4615
|
+
existingScript.addEventListener('load', onLoad);
|
|
4616
|
+
existingScript.addEventListener('error', () => {
|
|
4617
|
+
GoogleOneTap.scriptLoadPromise = null;
|
|
4618
|
+
reject(new Error('Failed to load Google One-Tap script'));
|
|
4619
|
+
});
|
|
4620
|
+
}
|
|
4621
|
+
});
|
|
4622
|
+
return GoogleOneTap.scriptLoadPromise;
|
|
4623
|
+
}
|
|
4624
|
+
// 创建新的脚本加载 Promise
|
|
4625
|
+
GoogleOneTap.scriptLoadPromise = new Promise((resolve, reject) => {
|
|
4626
|
+
this.log('Loading Google One-Tap script');
|
|
4627
|
+
const script = document.createElement('script');
|
|
4628
|
+
script.id = 'google-one-tap-script';
|
|
4629
|
+
script.src = 'https://accounts.google.com/gsi/client';
|
|
4630
|
+
script.async = true;
|
|
4631
|
+
script.defer = true;
|
|
4632
|
+
script.onload = () => {
|
|
4633
|
+
this.log('Google One-Tap script loaded successfully');
|
|
4634
|
+
// 等待 Google API 完全就绪
|
|
4635
|
+
this.waitForGoogleAPI()
|
|
4636
|
+
.then(() => {
|
|
4637
|
+
this.scriptLoaded = true;
|
|
4638
|
+
resolve();
|
|
4639
|
+
})
|
|
4640
|
+
.catch((error) => {
|
|
4641
|
+
GoogleOneTap.scriptLoadPromise = null;
|
|
4642
|
+
reject(error);
|
|
4643
|
+
});
|
|
4644
|
+
};
|
|
4645
|
+
script.onerror = (error) => {
|
|
4646
|
+
this.log('Failed to load Google One-Tap script:', error);
|
|
4647
|
+
GoogleOneTap.scriptLoadPromise = null;
|
|
4648
|
+
reject(new Error('Failed to load Google One-Tap script'));
|
|
4649
|
+
};
|
|
4650
|
+
document.head.appendChild(script);
|
|
4651
|
+
});
|
|
4652
|
+
return GoogleOneTap.scriptLoadPromise;
|
|
4653
|
+
}
|
|
4654
|
+
/**
|
|
4655
|
+
* 处理错误
|
|
4656
|
+
*/
|
|
4657
|
+
handleError(error) {
|
|
4658
|
+
this.log('Error:', error);
|
|
4659
|
+
if (this.options.onError) {
|
|
4660
|
+
try {
|
|
4661
|
+
this.options.onError(error);
|
|
4662
|
+
}
|
|
4663
|
+
catch (callbackError) {
|
|
4664
|
+
console.error('[GoogleOneTap] Error in onError callback:', callbackError);
|
|
4665
|
+
}
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
/**
|
|
4669
|
+
* 调试日志
|
|
4670
|
+
*/
|
|
4671
|
+
log(message, ...args) {
|
|
4672
|
+
if (this.options.debug) {
|
|
4673
|
+
console.log(`[GoogleOneTap] ${message}`, ...args);
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
/**
|
|
4677
|
+
* 检查 Google One-Tap 是否可用
|
|
4678
|
+
*/
|
|
4679
|
+
static isAvailable() {
|
|
4680
|
+
return typeof window !== 'undefined' && 'google' in window;
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
// 静态 Promise,用于避免多个实例重复加载脚本
|
|
4684
|
+
GoogleOneTap.scriptLoadPromise = null;
|
|
4685
|
+
|
|
4686
|
+
export { AuthFactory, AuthModal, AuthProvider, BuiltInHooks, ENVIRONMENT_CONFIGS, ErrorCode, GoogleOneTap, SeaVerseBackendAPIClient, Toast, createAuthModal, detectEnvironment, getEnvironmentConfig, models };
|
|
4326
4687
|
//# sourceMappingURL=index.js.map
|