@seaverse/auth-sdk 0.4.5 → 0.4.7

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/README.md CHANGED
@@ -1817,7 +1817,13 @@ MIT © [SeaVerse Team](mailto:support@seaverse.com)
1817
1817
 
1818
1818
  ## Changelog
1819
1819
 
1820
- ### v0.4.4 (Current Version)
1820
+ ### v0.4.6 (Current Version)
1821
+ - 🐛 **Bug Fix**: Fix OAuth buttons not responding in email login view
1822
+ - Fixed HTML element ID conflict between new style and old style login views
1823
+ - Old style login view OAuth buttons now have unique IDs with 'old' prefix
1824
+ - Properly bind click events to all OAuth buttons in both views
1825
+
1826
+ ### v0.4.4
1821
1827
  - 🎨 **Login UI Redesign**: Dual-view login system with progressive disclosure
1822
1828
  - **New Style View (Default)**: Modern OAuth-first interface with brand color #00E599
1823
1829
  - Google as primary button, GitHub/Discord as secondary options
package/dist/index.cjs CHANGED
@@ -2969,11 +2969,11 @@ class AuthModal {
2969
2969
  const socialGrid = document.createElement('div');
2970
2970
  socialGrid.className = 'social-buttons-grid';
2971
2971
  if (this.options.enableOAuth?.google) {
2972
- const googleBtn = this.createSocialButton('google', 'Google', 'login');
2972
+ const googleBtn = this.createSocialButton('google', 'Google', 'login', false, 'old');
2973
2973
  socialGrid.appendChild(googleBtn);
2974
2974
  }
2975
2975
  if (this.options.enableOAuth?.github) {
2976
- const githubBtn = this.createSocialButton('github', 'Github', 'login');
2976
+ const githubBtn = this.createSocialButton('github', 'Github', 'login', false, 'old');
2977
2977
  socialGrid.appendChild(githubBtn);
2978
2978
  }
2979
2979
  if (socialGrid.children.length > 0) {
@@ -2981,7 +2981,7 @@ class AuthModal {
2981
2981
  }
2982
2982
  // Discord button (full width)
2983
2983
  if (this.options.enableOAuth?.discord) {
2984
- const discordBtn = this.createSocialButton('discord', 'Discord', 'login', true);
2984
+ const discordBtn = this.createSocialButton('discord', 'Discord', 'login', true, 'old');
2985
2985
  form.appendChild(discordBtn);
2986
2986
  }
2987
2987
  }
@@ -3446,11 +3446,11 @@ class AuthModal {
3446
3446
  wrapper.appendChild(toggleBtn);
3447
3447
  return wrapper;
3448
3448
  }
3449
- createSocialButton(provider, label, mode, fullWidth = false) {
3449
+ createSocialButton(provider, label, mode, fullWidth = false, idPrefix = '') {
3450
3450
  const button = document.createElement('button');
3451
3451
  button.type = 'button';
3452
3452
  button.className = fullWidth ? 'btn-social btn-social-full' : 'btn-social';
3453
- button.id = `${provider}${mode === 'login' ? 'SignIn' : 'SignUp'}Modal`;
3453
+ button.id = `${idPrefix}${provider}${mode === 'login' ? 'SignIn' : 'SignUp'}Modal`;
3454
3454
  // SVG icons for each provider
3455
3455
  const icons = {
3456
3456
  google: `<svg width="20" height="20" viewBox="0 0 24 24">
@@ -3738,7 +3738,7 @@ class AuthModal {
3738
3738
  bindSocialLoginButtons() {
3739
3739
  if (!this.modal)
3740
3740
  return;
3741
- // Google login buttons
3741
+ // New style login view - Google login buttons
3742
3742
  const googleSignInBtn = this.modal.querySelector('#googleSignInModal');
3743
3743
  googleSignInBtn?.addEventListener('click', (e) => {
3744
3744
  e.preventDefault();
@@ -3749,7 +3749,7 @@ class AuthModal {
3749
3749
  e.preventDefault();
3750
3750
  this.startOAuthFlow('google');
3751
3751
  });
3752
- // Discord login buttons
3752
+ // New style login view - Discord login buttons
3753
3753
  const discordSignInBtn = this.modal.querySelector('#discordSignInModal');
3754
3754
  discordSignInBtn?.addEventListener('click', (e) => {
3755
3755
  e.preventDefault();
@@ -3760,7 +3760,7 @@ class AuthModal {
3760
3760
  e.preventDefault();
3761
3761
  this.startOAuthFlow('discord');
3762
3762
  });
3763
- // GitHub login buttons
3763
+ // New style login view - GitHub login buttons
3764
3764
  const githubSignInBtn = this.modal.querySelector('#githubSignInModal');
3765
3765
  githubSignInBtn?.addEventListener('click', (e) => {
3766
3766
  e.preventDefault();
@@ -3771,6 +3771,24 @@ class AuthModal {
3771
3771
  e.preventDefault();
3772
3772
  this.startOAuthFlow('github');
3773
3773
  });
3774
+ // Old style login view - Google login button
3775
+ const oldGoogleSignInBtn = this.modal.querySelector('#oldgoogleSignInModal');
3776
+ oldGoogleSignInBtn?.addEventListener('click', (e) => {
3777
+ e.preventDefault();
3778
+ this.startOAuthFlow('google');
3779
+ });
3780
+ // Old style login view - Discord login button
3781
+ const oldDiscordSignInBtn = this.modal.querySelector('#olddiscordSignInModal');
3782
+ oldDiscordSignInBtn?.addEventListener('click', (e) => {
3783
+ e.preventDefault();
3784
+ this.startOAuthFlow('discord');
3785
+ });
3786
+ // Old style login view - GitHub login button
3787
+ const oldGithubSignInBtn = this.modal.querySelector('#oldgithubSignInModal');
3788
+ oldGithubSignInBtn?.addEventListener('click', (e) => {
3789
+ e.preventDefault();
3790
+ this.startOAuthFlow('github');
3791
+ });
3774
3792
  }
3775
3793
  switchView(view) {
3776
3794
  if (!this.modal)
@@ -4324,11 +4342,373 @@ function createAuthModal(options) {
4324
4342
  return new AuthModal(options);
4325
4343
  }
4326
4344
 
4345
+ /**
4346
+ * Google One-Tap Sign-In SDK
4347
+ *
4348
+ * 用于在用户未登录 SeaVerse 但已登录 Google 的情况下,
4349
+ * 自动显示 Google One-Tap 快速登录界面
4350
+ */
4351
+ /**
4352
+ * Google One-Tap 快速登录
4353
+ *
4354
+ * 用法示例:
4355
+ * ```typescript
4356
+ * const googleLogin = new GoogleOneTap({
4357
+ * clientId: 'YOUR_GOOGLE_CLIENT_ID',
4358
+ * isLoggedIn: () => !!localStorage.getItem('token'),
4359
+ * onCredentialReceived: async (credential) => {
4360
+ * // 调用后端验证
4361
+ * const res = await fetch('/api/auth/google', {
4362
+ * method: 'POST',
4363
+ * body: JSON.stringify({ credential })
4364
+ * });
4365
+ * const data = await res.json();
4366
+ * localStorage.setItem('token', data.token);
4367
+ * window.location.reload();
4368
+ * }
4369
+ * });
4370
+ * ```
4371
+ */
4372
+ class GoogleOneTap {
4373
+ constructor(options) {
4374
+ this.isInitialized = false;
4375
+ this.scriptLoaded = false;
4376
+ this.options = options;
4377
+ this.log('GoogleOneTap initialized with options:', options);
4378
+ // 立即检查登录状态并初始化
4379
+ this.checkAndInit();
4380
+ }
4381
+ /**
4382
+ * 检查登录状态并初始化
4383
+ */
4384
+ checkAndInit() {
4385
+ // 检查用户是否已登录 SeaVerse
4386
+ if (this.options.isLoggedIn()) {
4387
+ this.log('User already logged in, skipping Google One-Tap initialization');
4388
+ return;
4389
+ }
4390
+ this.log('User not logged in, initializing Google One-Tap');
4391
+ // 未登录,加载并初始化 Google One-Tap
4392
+ this.loadGoogleScript()
4393
+ .then(() => {
4394
+ this.init();
4395
+ })
4396
+ .catch((error) => {
4397
+ this.handleError(new Error(`Failed to load Google One-Tap script: ${error.message}`));
4398
+ });
4399
+ }
4400
+ /**
4401
+ * 初始化 Google One-Tap API
4402
+ */
4403
+ init() {
4404
+ if (!window.google?.accounts?.id) {
4405
+ this.handleError(new Error('Google One-Tap API not available'));
4406
+ return;
4407
+ }
4408
+ try {
4409
+ // 初始化 Google One-Tap
4410
+ window.google.accounts.id.initialize({
4411
+ client_id: this.options.clientId,
4412
+ callback: (response) => {
4413
+ this.handleCredentialResponse(response);
4414
+ },
4415
+ auto_select: false,
4416
+ cancel_on_tap_outside: this.options.cancelOnTapOutside ?? true,
4417
+ context: this.options.context || 'signin',
4418
+ use_fedcm_for_prompt: this.options.useFedcmForPrompt ?? false
4419
+ });
4420
+ this.isInitialized = true;
4421
+ this.log('Google One-Tap initialized successfully');
4422
+ // 如果启用自动提示,显示 One-Tap
4423
+ if (this.options.autoPrompt !== false) {
4424
+ this.prompt();
4425
+ }
4426
+ }
4427
+ catch (error) {
4428
+ this.handleError(new Error(`Failed to initialize Google One-Tap: ${error.message}`));
4429
+ }
4430
+ }
4431
+ /**
4432
+ * 处理 Google 返回的 credential
4433
+ */
4434
+ async handleCredentialResponse(response) {
4435
+ this.log('Received credential from Google', {
4436
+ select_by: response.select_by,
4437
+ credential_length: response.credential?.length
4438
+ });
4439
+ // 验证 credential 是否存在
4440
+ if (!response.credential) {
4441
+ const error = new Error('No credential received from Google');
4442
+ this.log('Error: No credential in response', response);
4443
+ this.handleError(error);
4444
+ return;
4445
+ }
4446
+ try {
4447
+ await this.options.onCredentialReceived(response.credential);
4448
+ this.log('Credential processed successfully');
4449
+ }
4450
+ catch (error) {
4451
+ this.log('Error processing credential:', error);
4452
+ this.handleError(error);
4453
+ }
4454
+ }
4455
+ /**
4456
+ * 显示 Google One-Tap 提示
4457
+ * 会先检查登录状态
4458
+ */
4459
+ prompt() {
4460
+ // 再次检查登录状态(可能在初始化后用户已登录)
4461
+ if (this.options.isLoggedIn()) {
4462
+ this.log('User already logged in, skipping prompt');
4463
+ return;
4464
+ }
4465
+ if (!this.isInitialized) {
4466
+ this.log('Google One-Tap not initialized yet, skipping prompt');
4467
+ return;
4468
+ }
4469
+ if (!window.google?.accounts?.id) {
4470
+ this.handleError(new Error('Google One-Tap API not available'));
4471
+ return;
4472
+ }
4473
+ try {
4474
+ this.log('Displaying Google One-Tap prompt');
4475
+ window.google.accounts.id.prompt((notification) => {
4476
+ if (notification.isNotDisplayed()) {
4477
+ const reason = notification.getNotDisplayedReason();
4478
+ this.log('One-Tap not displayed:', reason);
4479
+ }
4480
+ else if (notification.isSkippedMoment()) {
4481
+ const reason = notification.getSkippedReason();
4482
+ this.log('One-Tap skipped:', reason);
4483
+ }
4484
+ else if (notification.isDismissedMoment()) {
4485
+ const reason = notification.getDismissedReason();
4486
+ this.log('One-Tap dismissed:', reason);
4487
+ }
4488
+ else if (notification.isDisplayed()) {
4489
+ this.log('One-Tap displayed successfully');
4490
+ }
4491
+ });
4492
+ }
4493
+ catch (error) {
4494
+ this.handleError(new Error(`Failed to show Google One-Tap: ${error.message}`));
4495
+ }
4496
+ }
4497
+ /**
4498
+ * 取消 Google One-Tap 提示
4499
+ */
4500
+ cancel() {
4501
+ if (!this.isInitialized) {
4502
+ this.log('Google One-Tap not initialized, nothing to cancel');
4503
+ return;
4504
+ }
4505
+ if (!window.google?.accounts?.id) {
4506
+ return;
4507
+ }
4508
+ try {
4509
+ window.google.accounts.id.cancel();
4510
+ this.log('Google One-Tap canceled');
4511
+ }
4512
+ catch (error) {
4513
+ this.log('Error canceling Google One-Tap:', error);
4514
+ }
4515
+ }
4516
+ /**
4517
+ * 渲染 Google 登录按钮到指定元素
4518
+ *
4519
+ * @param element - 要渲染按钮的 HTML 元素
4520
+ *
4521
+ * @example
4522
+ * const container = document.getElementById('google-btn');
4523
+ * googleLogin.renderButton(container);
4524
+ */
4525
+ renderButton(element) {
4526
+ // 检查登录状态
4527
+ if (this.options.isLoggedIn()) {
4528
+ this.log('User already logged in, not rendering button');
4529
+ return;
4530
+ }
4531
+ if (!this.isInitialized) {
4532
+ this.log('Google One-Tap not initialized yet, waiting...');
4533
+ // 等待初始化完成后再渲染
4534
+ setTimeout(() => this.renderButton(element), 100);
4535
+ return;
4536
+ }
4537
+ if (!window.google?.accounts?.id) {
4538
+ this.handleError(new Error('Google One-Tap API not available'));
4539
+ return;
4540
+ }
4541
+ try {
4542
+ const buttonConfig = {
4543
+ type: this.options.button?.type || 'standard',
4544
+ theme: this.options.button?.theme || 'outline',
4545
+ size: this.options.button?.size || 'large',
4546
+ text: this.options.button?.text || 'signin_with',
4547
+ shape: this.options.button?.shape || 'rectangular',
4548
+ logo_alignment: this.options.button?.logoAlignment || 'left',
4549
+ width: this.options.button?.width,
4550
+ locale: this.options.button?.locale
4551
+ };
4552
+ window.google.accounts.id.renderButton(element, buttonConfig);
4553
+ this.log('Google login button rendered', buttonConfig);
4554
+ }
4555
+ catch (error) {
4556
+ this.handleError(new Error(`Failed to render Google button: ${error.message}`));
4557
+ }
4558
+ }
4559
+ /**
4560
+ * 禁用自动选择
4561
+ * 用户关闭 One-Tap 后,下次不会自动选择账号
4562
+ */
4563
+ disableAutoSelect() {
4564
+ if (!window.google?.accounts?.id) {
4565
+ return;
4566
+ }
4567
+ try {
4568
+ window.google.accounts.id.disableAutoSelect();
4569
+ this.log('Auto select disabled');
4570
+ }
4571
+ catch (error) {
4572
+ this.log('Error disabling auto select:', error);
4573
+ }
4574
+ }
4575
+ /**
4576
+ * 等待 Google API 完全就绪
4577
+ */
4578
+ waitForGoogleAPI(maxAttempts = 50, interval = 100) {
4579
+ return new Promise((resolve, reject) => {
4580
+ let attempts = 0;
4581
+ const checkAPI = () => {
4582
+ if (window.google?.accounts?.id) {
4583
+ resolve();
4584
+ return;
4585
+ }
4586
+ attempts++;
4587
+ if (attempts >= maxAttempts) {
4588
+ reject(new Error('Google One-Tap API not available after timeout'));
4589
+ return;
4590
+ }
4591
+ setTimeout(checkAPI, interval);
4592
+ };
4593
+ checkAPI();
4594
+ });
4595
+ }
4596
+ /**
4597
+ * 加载 Google One-Tap 脚本
4598
+ */
4599
+ loadGoogleScript() {
4600
+ // 如果已经有加载中的 Promise,直接返回
4601
+ if (GoogleOneTap.scriptLoadPromise) {
4602
+ return GoogleOneTap.scriptLoadPromise;
4603
+ }
4604
+ // 如果脚本已加载完成且 API 可用,创建已 resolve 的 Promise
4605
+ if (this.scriptLoaded && window.google?.accounts?.id) {
4606
+ return Promise.resolve();
4607
+ }
4608
+ // 检查脚本标签是否已存在
4609
+ const existingScript = document.getElementById('google-one-tap-script');
4610
+ if (existingScript) {
4611
+ // 如果脚本标签存在,等待其加载完成
4612
+ GoogleOneTap.scriptLoadPromise = new Promise((resolve, reject) => {
4613
+ if (window.google?.accounts?.id) {
4614
+ this.scriptLoaded = true;
4615
+ resolve();
4616
+ return;
4617
+ }
4618
+ // 等待脚本加载
4619
+ const onLoad = () => {
4620
+ // 等待 Google API 就绪
4621
+ this.waitForGoogleAPI()
4622
+ .then(() => {
4623
+ this.scriptLoaded = true;
4624
+ resolve();
4625
+ })
4626
+ .catch(reject);
4627
+ };
4628
+ // 检查脚本是否已经加载完成(通过检查 API 是否可用)
4629
+ // 如果 API 已经可用,直接调用 onLoad
4630
+ if (window.google?.accounts?.id) {
4631
+ onLoad();
4632
+ }
4633
+ else {
4634
+ // 否则等待脚本加载事件
4635
+ existingScript.addEventListener('load', onLoad);
4636
+ existingScript.addEventListener('error', () => {
4637
+ GoogleOneTap.scriptLoadPromise = null;
4638
+ reject(new Error('Failed to load Google One-Tap script'));
4639
+ });
4640
+ }
4641
+ });
4642
+ return GoogleOneTap.scriptLoadPromise;
4643
+ }
4644
+ // 创建新的脚本加载 Promise
4645
+ GoogleOneTap.scriptLoadPromise = new Promise((resolve, reject) => {
4646
+ this.log('Loading Google One-Tap script');
4647
+ const script = document.createElement('script');
4648
+ script.id = 'google-one-tap-script';
4649
+ script.src = 'https://accounts.google.com/gsi/client';
4650
+ script.async = true;
4651
+ script.defer = true;
4652
+ script.onload = () => {
4653
+ this.log('Google One-Tap script loaded successfully');
4654
+ // 等待 Google API 完全就绪
4655
+ this.waitForGoogleAPI()
4656
+ .then(() => {
4657
+ this.scriptLoaded = true;
4658
+ resolve();
4659
+ })
4660
+ .catch((error) => {
4661
+ GoogleOneTap.scriptLoadPromise = null;
4662
+ reject(error);
4663
+ });
4664
+ };
4665
+ script.onerror = (error) => {
4666
+ this.log('Failed to load Google One-Tap script:', error);
4667
+ GoogleOneTap.scriptLoadPromise = null;
4668
+ reject(new Error('Failed to load Google One-Tap script'));
4669
+ };
4670
+ document.head.appendChild(script);
4671
+ });
4672
+ return GoogleOneTap.scriptLoadPromise;
4673
+ }
4674
+ /**
4675
+ * 处理错误
4676
+ */
4677
+ handleError(error) {
4678
+ this.log('Error:', error);
4679
+ if (this.options.onError) {
4680
+ try {
4681
+ this.options.onError(error);
4682
+ }
4683
+ catch (callbackError) {
4684
+ console.error('[GoogleOneTap] Error in onError callback:', callbackError);
4685
+ }
4686
+ }
4687
+ }
4688
+ /**
4689
+ * 调试日志
4690
+ */
4691
+ log(message, ...args) {
4692
+ if (this.options.debug) {
4693
+ console.log(`[GoogleOneTap] ${message}`, ...args);
4694
+ }
4695
+ }
4696
+ /**
4697
+ * 检查 Google One-Tap 是否可用
4698
+ */
4699
+ static isAvailable() {
4700
+ return typeof window !== 'undefined' && 'google' in window;
4701
+ }
4702
+ }
4703
+ // 静态 Promise,用于避免多个实例重复加载脚本
4704
+ GoogleOneTap.scriptLoadPromise = null;
4705
+
4327
4706
  exports.AuthFactory = AuthFactory;
4328
4707
  exports.AuthModal = AuthModal;
4329
4708
  exports.AuthProvider = AuthProvider;
4330
4709
  exports.BuiltInHooks = BuiltInHooks;
4331
4710
  exports.ENVIRONMENT_CONFIGS = ENVIRONMENT_CONFIGS;
4711
+ exports.GoogleOneTap = GoogleOneTap;
4332
4712
  exports.SeaVerseBackendAPIClient = SeaVerseBackendAPIClient;
4333
4713
  exports.Toast = Toast;
4334
4714
  exports.createAuthModal = createAuthModal;