@quantabit/oauth-sdk 1.0.0

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.
@@ -0,0 +1,1124 @@
1
+ import { BaseApiClient } from '@quantabit/sdk-config';
2
+ import React, { useState, useCallback, useEffect } from 'react';
3
+
4
+ /**
5
+ * OAuth SDK - API 客户端
6
+ * OAuth 认证系统后端接口封装
7
+ *
8
+ * 使用 BaseApiClient 基类简化代码
9
+ */
10
+
11
+
12
+ /**
13
+ * OAuth API 客户端
14
+ */
15
+ class OauthApiClient extends BaseApiClient {
16
+ constructor(config = {}) {
17
+ super('/oauth', config);
18
+ }
19
+ _normalizeProviderArgs(providerOrOptions, options = {}) {
20
+ if (typeof providerOrOptions === 'object' && providerOrOptions !== null) {
21
+ const {
22
+ provider,
23
+ ...params
24
+ } = providerOrOptions;
25
+ return {
26
+ provider,
27
+ params
28
+ };
29
+ }
30
+ return {
31
+ provider: providerOrOptions,
32
+ params: options
33
+ };
34
+ }
35
+
36
+ // ============ OAuth 登录 ============
37
+
38
+ /**
39
+ * 获取 OAuth 登录 URL
40
+ * @param {string} provider - 提供商(google, github, twitter, discord 等)
41
+ * @param {Object} options - 选项
42
+ */
43
+ async getAuthUrl(provider, options = {}) {
44
+ const normalized = this._normalizeProviderArgs(provider, options);
45
+ return this.get(`/${normalized.provider}/auth-url`, normalized.params);
46
+ }
47
+
48
+ /**
49
+ * OAuth 回调处理
50
+ * @param {string} provider - 提供商
51
+ * @param {string} code - 授权码
52
+ * @param {string} state - 状态
53
+ */
54
+ async handleCallback(provider, code, state) {
55
+ if (typeof provider === 'object' && provider !== null) {
56
+ const {
57
+ provider: callbackProvider,
58
+ code: callbackCode,
59
+ state: callbackState
60
+ } = provider;
61
+ return this.post(`/${callbackProvider}/callback`, {
62
+ code: callbackCode,
63
+ state: callbackState
64
+ });
65
+ }
66
+ return this.post(`/${provider}/callback`, {
67
+ code,
68
+ state
69
+ });
70
+ }
71
+
72
+ /**
73
+ * 获取支持的 OAuth 提供商
74
+ */
75
+ async getProviders() {
76
+ return this.get('/providers');
77
+ }
78
+
79
+ // ============ 账号绑定 ============
80
+
81
+ /**
82
+ * 绑定第三方账号
83
+ * @param {string} provider - 提供商
84
+ * @param {string} code - 授权码
85
+ */
86
+ async bindAccount(provider, code) {
87
+ return this.post(`/${provider}/bind`, {
88
+ code
89
+ });
90
+ }
91
+
92
+ /**
93
+ * 解绑第三方账号
94
+ * @param {string} provider - 提供商
95
+ */
96
+ async unbindAccount(provider) {
97
+ return this.delete(`/${provider}/bind`);
98
+ }
99
+
100
+ /**
101
+ * 解绑第三方账号(兼容 hooks 命名)
102
+ * @param {string} provider - 提供商
103
+ */
104
+ async unlinkAccount(provider) {
105
+ return this.unbindAccount(provider);
106
+ }
107
+
108
+ /**
109
+ * 获取已绑定账号
110
+ */
111
+ async getBoundAccounts() {
112
+ return this.get('/accounts');
113
+ }
114
+
115
+ /**
116
+ * 获取已关联账号(兼容旧版快捷方法命名)
117
+ */
118
+ async getLinkedAccounts() {
119
+ return this.getBoundAccounts();
120
+ }
121
+
122
+ /**
123
+ * 检查账号绑定状态
124
+ * @param {string} provider - 提供商
125
+ */
126
+ async checkBindStatus(provider) {
127
+ return this.get(`/${provider}/bind/status`);
128
+ }
129
+
130
+ // ============ 钱包连接 ============
131
+
132
+ /**
133
+ * 获取钱包连接消息
134
+ * @param {string} address - 钱包地址
135
+ */
136
+ async getWalletMessage(address) {
137
+ return this.post('/wallet/message', {
138
+ address
139
+ });
140
+ }
141
+
142
+ /**
143
+ * 验证钱包签名
144
+ * @param {string} address - 钱包地址
145
+ * @param {string} signature - 签名
146
+ * @param {string} message - 消息
147
+ */
148
+ async verifyWalletSignature(address, signature, message) {
149
+ return this.post('/wallet/verify', {
150
+ address,
151
+ signature,
152
+ message
153
+ });
154
+ }
155
+
156
+ /**
157
+ * 使用钱包登录
158
+ * @param {string} address - 钱包地址
159
+ * @param {string} signature - 签名
160
+ * @param {string} message - 消息
161
+ */
162
+ async loginWithWallet(address, signature, message) {
163
+ return this.post('/wallet/login', {
164
+ address,
165
+ signature,
166
+ message
167
+ });
168
+ }
169
+
170
+ /**
171
+ * 绑定钱包
172
+ * @param {string} address - 钱包地址
173
+ * @param {string} signature - 签名
174
+ * @param {string} message - 消息
175
+ */
176
+ async bindWallet(address, signature, message) {
177
+ return this.post('/wallet/bind', {
178
+ address,
179
+ signature,
180
+ message
181
+ });
182
+ }
183
+
184
+ /**
185
+ * 解绑钱包
186
+ * @param {string} address - 钱包地址
187
+ */
188
+ async unbindWallet(address) {
189
+ return this.delete('/wallet/bind', {
190
+ address
191
+ });
192
+ }
193
+
194
+ /**
195
+ * 获取已绑定钱包
196
+ */
197
+ async getBoundWallets() {
198
+ return this.get('/wallet/list');
199
+ }
200
+
201
+ // ============ SSO 单点登录 ============
202
+
203
+ /**
204
+ * 获取 SSO 登录 URL
205
+ * @param {Object} options - 选项
206
+ */
207
+ async getSSOUrl(options = {}) {
208
+ return this.get('/sso/url', options);
209
+ }
210
+
211
+ /**
212
+ * SSO 登录验证
213
+ * @param {string} token - SSO Token
214
+ */
215
+ async verifySSOToken(token) {
216
+ return this.post('/sso/verify', {
217
+ token
218
+ });
219
+ }
220
+
221
+ /**
222
+ * SSO 登出
223
+ */
224
+ async ssoLogout() {
225
+ return this.post('/sso/logout');
226
+ }
227
+
228
+ /**
229
+ * 检查当前 OAuth 登录态
230
+ */
231
+ async checkAuth() {
232
+ return this.get('/auth/check');
233
+ }
234
+
235
+ /**
236
+ * 登出当前 OAuth 会话
237
+ */
238
+ async logout() {
239
+ return this.post('/logout');
240
+ }
241
+
242
+ // ============ Token 管理 ============
243
+
244
+ /**
245
+ * 刷新 Token
246
+ * @param {string} refreshToken - 刷新 Token
247
+ */
248
+ async refreshToken(refreshToken) {
249
+ return this.post('/token/refresh', {
250
+ refresh_token: refreshToken
251
+ });
252
+ }
253
+
254
+ /**
255
+ * 撤销 Token
256
+ * @param {string} token - Token
257
+ */
258
+ async revokeToken(token) {
259
+ return this.post('/token/revoke', {
260
+ token
261
+ });
262
+ }
263
+
264
+ /**
265
+ * 获取 Token 信息
266
+ * @param {string} token - Token
267
+ */
268
+ async getTokenInfo(token) {
269
+ return this.post('/token/info', {
270
+ token
271
+ });
272
+ }
273
+
274
+ // ============ 授权管理 ============
275
+
276
+ /**
277
+ * 获取授权应用列表
278
+ */
279
+ async getAuthorizedApps() {
280
+ return this.get('/apps');
281
+ }
282
+
283
+ /**
284
+ * 撤销应用授权
285
+ * @param {string} appId - 应用 ID
286
+ */
287
+ async revokeAppAuthorization(appId) {
288
+ return this.delete(`/apps/${appId}`);
289
+ }
290
+ }
291
+
292
+ // 创建默认实例
293
+ const oauthApi = new OauthApiClient();
294
+ const OAuthApiClient = OauthApiClient;
295
+
296
+ /**
297
+ * OAuth SDK - 类型定义
298
+ */
299
+
300
+ // 第三方登录提供商
301
+ const OAuthProvider = {
302
+ // 社交
303
+ GOOGLE: 'google',
304
+ FACEBOOK: 'facebook',
305
+ TWITTER: 'twitter',
306
+ GITHUB: 'github',
307
+ DISCORD: 'discord',
308
+ TELEGRAM: 'telegram',
309
+ WECHAT: 'wechat',
310
+ WEIBO: 'weibo',
311
+ QQ: 'qq',
312
+ APPLE: 'apple',
313
+ // 钱包
314
+ METAMASK: 'metamask',
315
+ WALLET_CONNECT: 'wallet_connect',
316
+ COINBASE_WALLET: 'coinbase_wallet',
317
+ PHANTOM: 'phantom',
318
+ // 企业
319
+ MICROSOFT: 'microsoft',
320
+ OKTA: 'okta',
321
+ AUTH0: 'auth0'
322
+ };
323
+
324
+ // 连接状态
325
+ const ConnectionStatus = {
326
+ DISCONNECTED: 'disconnected',
327
+ CONNECTING: 'connecting',
328
+ CONNECTED: 'connected',
329
+ ERROR: 'error'
330
+ };
331
+
332
+ // 账户绑定状态
333
+ const BindingStatus = {
334
+ NOT_BOUND: 'not_bound',
335
+ BOUND: 'bound',
336
+ PENDING: 'pending'
337
+ };
338
+
339
+ // 授权范围
340
+ const OAuthScope = {
341
+ PROFILE: 'profile',
342
+ EMAIL: 'email',
343
+ OPENID: 'openid',
344
+ READ: 'read',
345
+ WRITE: 'write'
346
+ };
347
+
348
+ /**
349
+ * OAuth SDK - 国际化
350
+ * OAuth授权系统多语言支持
351
+ */
352
+
353
+ const SUPPORTED_LANGUAGES = ['en', 'zh', 'ja', 'ko'];
354
+ const messages = {
355
+ zh: {
356
+ // 登录
357
+ login: '登录',
358
+ loginWith: '使用 {provider} 登录',
359
+ loginTo: '登录到',
360
+ signIn: '登录',
361
+ signUp: '注册',
362
+ signOut: '退出登录',
363
+ logout: '退出',
364
+ // 第三方
365
+ google: 'Google',
366
+ apple: 'Apple',
367
+ github: 'GitHub',
368
+ twitter: 'Twitter',
369
+ facebook: 'Facebook',
370
+ discord: 'Discord',
371
+ wechat: '微信',
372
+ qq: 'QQ',
373
+ // 授权
374
+ authorize: '授权',
375
+ authorization: '授权',
376
+ authorizing: '授权中...',
377
+ allowAccess: '允许访问',
378
+ denyAccess: '拒绝访问',
379
+ // 权限
380
+ permissions: '权限',
381
+ requestingPermissions: '请求以下权限',
382
+ readProfile: '读取基本资料',
383
+ readEmail: '读取邮箱地址',
384
+ writeData: '写入数据',
385
+ // 连接
386
+ connect: '连接',
387
+ connected: '已连接',
388
+ disconnect: '断开连接',
389
+ connectionRequired: '需要连接账户',
390
+ // 状态
391
+ loading: '加载中...',
392
+ redirecting: '跳转中...',
393
+ success: '授权成功',
394
+ failed: '授权失败',
395
+ error: '发生错误',
396
+ tryAgain: '重试',
397
+ // 账户
398
+ linkedAccounts: '已关联账户',
399
+ linkAccount: '关联账户',
400
+ unlinkAccount: '解除关联',
401
+ noLinkedAccounts: '暂无关联账户',
402
+ // 安全
403
+ secureLogin: '安全登录',
404
+ securedBy: '由 {provider} 提供安全保障',
405
+ // 协议
406
+ termsOfService: '服务条款',
407
+ privacyPolicy: '隐私政策',
408
+ byLoggingIn: '登录即表示您同意我们的',
409
+ and: '和',
410
+ // 返回
411
+ cancel: '取消',
412
+ back: '返回',
413
+ continueAs: '以 {name} 继续'
414
+ },
415
+ en: {
416
+ login: 'Login',
417
+ loginWith: 'Login with {provider}',
418
+ loginTo: 'Login to',
419
+ signIn: 'Sign In',
420
+ signUp: 'Sign Up',
421
+ signOut: 'Sign Out',
422
+ logout: 'Logout',
423
+ google: 'Google',
424
+ apple: 'Apple',
425
+ github: 'GitHub',
426
+ twitter: 'Twitter',
427
+ facebook: 'Facebook',
428
+ discord: 'Discord',
429
+ wechat: 'WeChat',
430
+ qq: 'QQ',
431
+ authorize: 'Authorize',
432
+ authorization: 'Authorization',
433
+ authorizing: 'Authorizing...',
434
+ allowAccess: 'Allow Access',
435
+ denyAccess: 'Deny Access',
436
+ permissions: 'Permissions',
437
+ requestingPermissions: 'Requesting the following permissions',
438
+ readProfile: 'Read your profile',
439
+ readEmail: 'Read your email',
440
+ writeData: 'Write data',
441
+ connect: 'Connect',
442
+ connected: 'Connected',
443
+ disconnect: 'Disconnect',
444
+ connectionRequired: 'Connection required',
445
+ loading: 'Loading...',
446
+ redirecting: 'Redirecting...',
447
+ success: 'Success',
448
+ failed: 'Failed',
449
+ error: 'Error',
450
+ tryAgain: 'Try Again',
451
+ linkedAccounts: 'Linked Accounts',
452
+ linkAccount: 'Link Account',
453
+ unlinkAccount: 'Unlink',
454
+ noLinkedAccounts: 'No linked accounts',
455
+ secureLogin: 'Secure Login',
456
+ securedBy: 'Secured by {provider}',
457
+ termsOfService: 'Terms of Service',
458
+ privacyPolicy: 'Privacy Policy',
459
+ byLoggingIn: 'By logging in, you agree to our',
460
+ and: 'and',
461
+ cancel: 'Cancel',
462
+ back: 'Back',
463
+ continueAs: 'Continue as {name}'
464
+ },
465
+ ja: {
466
+ login: 'ログイン',
467
+ loginWith: '{provider} でログイン',
468
+ loginTo: 'ログイン先',
469
+ signIn: 'サインイン',
470
+ signUp: 'サインアップ',
471
+ signOut: 'サインアウト',
472
+ logout: 'ログアウト',
473
+ google: 'Google',
474
+ apple: 'Apple',
475
+ github: 'GitHub',
476
+ twitter: 'Twitter',
477
+ facebook: 'Facebook',
478
+ discord: 'Discord',
479
+ wechat: 'WeChat',
480
+ qq: 'QQ',
481
+ authorize: '認可',
482
+ authorization: '認可',
483
+ authorizing: '認可中...',
484
+ allowAccess: 'アクセスを許可',
485
+ denyAccess: 'アクセスを拒否',
486
+ permissions: '権限',
487
+ requestingPermissions: '以下の権限を要求しています',
488
+ readProfile: 'プロフィールの読み取り',
489
+ readEmail: 'メールの読み取り',
490
+ writeData: 'データの書き込み',
491
+ connect: '接続',
492
+ connected: '接続済み',
493
+ disconnect: '切断',
494
+ connectionRequired: '接続が必要',
495
+ loading: '読み込み中...',
496
+ redirecting: 'リダイレクト中...',
497
+ success: '成功',
498
+ failed: '失敗',
499
+ error: 'エラー',
500
+ tryAgain: '再試行',
501
+ linkedAccounts: '連携済みアカウント',
502
+ linkAccount: 'アカウントを連携',
503
+ unlinkAccount: '連携解除',
504
+ noLinkedAccounts: '連携アカウントなし',
505
+ secureLogin: 'セキュアログイン',
506
+ securedBy: '{provider} によるセキュリティ',
507
+ termsOfService: '利用規約',
508
+ privacyPolicy: 'プライバシーポリシー',
509
+ byLoggingIn: 'ログインすると、以下に同意することになります:',
510
+ and: 'と',
511
+ cancel: 'キャンセル',
512
+ back: '戻る',
513
+ continueAs: '{name} として続行'
514
+ },
515
+ ko: {
516
+ login: '로그인',
517
+ loginWith: '{provider}로 로그인',
518
+ loginTo: '로그인 대상',
519
+ signIn: '로그인',
520
+ signUp: '가입하기',
521
+ signOut: '로그아웃',
522
+ logout: '로그아웃',
523
+ google: 'Google',
524
+ apple: 'Apple',
525
+ github: 'GitHub',
526
+ twitter: 'Twitter',
527
+ facebook: 'Facebook',
528
+ discord: 'Discord',
529
+ wechat: 'WeChat',
530
+ qq: 'QQ',
531
+ authorize: '승인',
532
+ authorization: '승인',
533
+ authorizing: '승인 중...',
534
+ allowAccess: '접근 허용',
535
+ denyAccess: '접근 거부',
536
+ permissions: '권한',
537
+ requestingPermissions: '다음 권한을 요청합니다',
538
+ readProfile: '프로필 읽기',
539
+ readEmail: '이메일 읽기',
540
+ writeData: '데이터 쓰기',
541
+ connect: '연결',
542
+ connected: '연결됨',
543
+ disconnect: '연결 해제',
544
+ connectionRequired: '연결 필요',
545
+ loading: '로딩 중...',
546
+ redirecting: '리다이렉트 중...',
547
+ success: '성공',
548
+ failed: '실패',
549
+ error: '오류',
550
+ tryAgain: '다시 시도',
551
+ linkedAccounts: '연결된 계정',
552
+ linkAccount: '계정 연결',
553
+ unlinkAccount: '연결 해제',
554
+ noLinkedAccounts: '연결된 계정 없음',
555
+ secureLogin: '보안 로그인',
556
+ securedBy: '{provider}에 의해 보안됨',
557
+ termsOfService: '서비스 약관',
558
+ privacyPolicy: '개인정보 처리방침',
559
+ byLoggingIn: '로그인하면 다음에 동의하는 것입니다:',
560
+ and: '및',
561
+ cancel: '취소',
562
+ back: '뒤로',
563
+ continueAs: '{name}(으)로 계속'
564
+ }
565
+ };
566
+ let currentLanguage = 'zh';
567
+ function setLanguage(lang) {
568
+ if (SUPPORTED_LANGUAGES.includes(lang)) currentLanguage = lang;
569
+ }
570
+ function getLanguage() {
571
+ return currentLanguage;
572
+ }
573
+ function t(key, params = {}) {
574
+ let text = (messages[currentLanguage] || messages.en)[key] || key;
575
+ Object.entries(params).forEach(([k, v]) => {
576
+ text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), v);
577
+ });
578
+ return text;
579
+ }
580
+
581
+ /**
582
+ * OAuth SDK - React Hooks
583
+ * OAuth授权系统相关的状态管理
584
+ */
585
+
586
+
587
+ /**
588
+ * OAuth登录
589
+ */
590
+ function useOAuthLogin() {
591
+ const [loading, setLoading] = useState(false);
592
+ const [error, setError] = useState(null);
593
+ const login = useCallback(async (provider, options = {}) => {
594
+ try {
595
+ setLoading(true);
596
+ setError(null);
597
+ const {
598
+ redirectUri,
599
+ scope,
600
+ state
601
+ } = options;
602
+
603
+ // 获取授权URL
604
+ const response = await oauthApi.getAuthUrl({
605
+ provider,
606
+ redirectUri: redirectUri || window.location.origin + '/oauth/callback',
607
+ scope,
608
+ state
609
+ });
610
+
611
+ // 打开授权窗口或跳转
612
+ if (options.popup) {
613
+ return openPopup(response.authUrl, provider);
614
+ } else {
615
+ window.location.href = response.authUrl;
616
+ }
617
+ } catch (err) {
618
+ setError(err.message);
619
+ throw err;
620
+ } finally {
621
+ setLoading(false);
622
+ }
623
+ }, []);
624
+ return {
625
+ login,
626
+ loading,
627
+ error
628
+ };
629
+ }
630
+
631
+ /**
632
+ * OAuth回调处理
633
+ */
634
+ function useOAuthCallback() {
635
+ const [status, setStatus] = useState('idle'); // idle, loading, success, error
636
+ const [user, setUser] = useState(null);
637
+ const [error, setError] = useState(null);
638
+ const handleCallback = useCallback(async (code, state, provider) => {
639
+ try {
640
+ setStatus('loading');
641
+ setError(null);
642
+ const response = await oauthApi.handleCallback({
643
+ code,
644
+ state,
645
+ provider
646
+ });
647
+ setUser(response.user);
648
+ setStatus('success');
649
+ return response;
650
+ } catch (err) {
651
+ setError(err.message);
652
+ setStatus('error');
653
+ throw err;
654
+ }
655
+ }, []);
656
+
657
+ // 自动处理URL参数
658
+ useEffect(() => {
659
+ const params = new URLSearchParams(window.location.search);
660
+ const code = params.get('code');
661
+ const state = params.get('state');
662
+ const provider = params.get('provider');
663
+ if (code) {
664
+ handleCallback(code, state, provider);
665
+ }
666
+ }, [handleCallback]);
667
+ return {
668
+ status,
669
+ user,
670
+ error,
671
+ handleCallback
672
+ };
673
+ }
674
+
675
+ /**
676
+ * 已关联账户
677
+ */
678
+ function useLinkedAccounts() {
679
+ const [accounts, setAccounts] = useState([]);
680
+ const [loading, setLoading] = useState(true);
681
+ const [error, setError] = useState(null);
682
+ const fetchAccounts = useCallback(async () => {
683
+ try {
684
+ setLoading(true);
685
+ const response = await oauthApi.getLinkedAccounts();
686
+ setAccounts(response.data || []);
687
+ setError(null);
688
+ } catch (err) {
689
+ setError(err.message);
690
+ } finally {
691
+ setLoading(false);
692
+ }
693
+ }, []);
694
+ useEffect(() => {
695
+ fetchAccounts();
696
+ }, [fetchAccounts]);
697
+ const linkAccount = useCallback(async provider => {
698
+ const response = await oauthApi.getAuthUrl({
699
+ provider,
700
+ redirectUri: window.location.origin + '/oauth/link-callback',
701
+ isLink: true
702
+ });
703
+ window.location.href = response.authUrl;
704
+ }, []);
705
+ const unlinkAccount = useCallback(async provider => {
706
+ try {
707
+ await oauthApi.unlinkAccount(provider);
708
+ setAccounts(prev => prev.filter(a => a.provider !== provider));
709
+ } catch (err) {
710
+ console.error('Unlink error:', err);
711
+ throw err;
712
+ }
713
+ }, []);
714
+ const isLinked = useCallback(provider => {
715
+ return accounts.some(a => a.provider === provider);
716
+ }, [accounts]);
717
+ return {
718
+ accounts,
719
+ loading,
720
+ error,
721
+ linkAccount,
722
+ unlinkAccount,
723
+ isLinked,
724
+ refresh: fetchAccounts
725
+ };
726
+ }
727
+
728
+ /**
729
+ * OAuth状态
730
+ */
731
+ function useOAuthState() {
732
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
733
+ const [user, setUser] = useState(null);
734
+ const [loading, setLoading] = useState(true);
735
+ const checkAuth = useCallback(async () => {
736
+ try {
737
+ setLoading(true);
738
+ const response = await oauthApi.checkAuth();
739
+ setIsAuthenticated(response.isAuthenticated);
740
+ setUser(response.user);
741
+ } catch (err) {
742
+ setIsAuthenticated(false);
743
+ setUser(null);
744
+ } finally {
745
+ setLoading(false);
746
+ }
747
+ }, []);
748
+ useEffect(() => {
749
+ checkAuth();
750
+ }, [checkAuth]);
751
+ const logout = useCallback(async () => {
752
+ try {
753
+ await oauthApi.logout();
754
+ setIsAuthenticated(false);
755
+ setUser(null);
756
+ } catch (err) {
757
+ console.error('Logout error:', err);
758
+ }
759
+ }, []);
760
+ return {
761
+ isAuthenticated,
762
+ user,
763
+ loading,
764
+ logout,
765
+ refresh: checkAuth
766
+ };
767
+ }
768
+
769
+ /**
770
+ * 可用的OAuth提供商
771
+ */
772
+ function useOAuthProviders() {
773
+ const [providers, setProviders] = useState([]);
774
+ const [loading, setLoading] = useState(true);
775
+ const fetchProviders = useCallback(async () => {
776
+ try {
777
+ setLoading(true);
778
+ const response = await oauthApi.getProviders();
779
+ setProviders(response.data || []);
780
+ } catch (err) {
781
+ console.error('Get providers error:', err);
782
+ } finally {
783
+ setLoading(false);
784
+ }
785
+ }, []);
786
+ useEffect(() => {
787
+ fetchProviders();
788
+ }, [fetchProviders]);
789
+ return {
790
+ providers,
791
+ loading
792
+ };
793
+ }
794
+
795
+ /**
796
+ * 弹窗登录
797
+ */
798
+ function openPopup(url, name) {
799
+ const width = 500;
800
+ const height = 600;
801
+ const left = (window.innerWidth - width) / 2 + window.screenX;
802
+ const top = (window.innerHeight - height) / 2 + window.screenY;
803
+ const popup = window.open(url, name, `width=${width},height=${height},left=${left},top=${top}`);
804
+ return new Promise((resolve, reject) => {
805
+ const checkClosed = setInterval(() => {
806
+ if (!popup || popup.closed) {
807
+ clearInterval(checkClosed);
808
+ reject(new Error('Login window closed'));
809
+ }
810
+ }, 500);
811
+
812
+ // 监听消息
813
+ const handleMessage = event => {
814
+ if (event.origin !== window.location.origin) return;
815
+ if (event.data?.type === 'oauth_callback') {
816
+ clearInterval(checkClosed);
817
+ window.removeEventListener('message', handleMessage);
818
+ popup?.close();
819
+ if (event.data.error) {
820
+ reject(new Error(event.data.error));
821
+ } else {
822
+ resolve(event.data.result);
823
+ }
824
+ }
825
+ };
826
+ window.addEventListener('message', handleMessage);
827
+ });
828
+ }
829
+
830
+ /**
831
+ * OAuth SDK - React 组件
832
+ * OAuth授权系统可视化组件
833
+ */
834
+
835
+
836
+ // 提供商图标
837
+ const providerIcons = {
838
+ google: '🔵',
839
+ apple: '🍎',
840
+ github: '⚫',
841
+ twitter: '🐦',
842
+ facebook: '📘',
843
+ discord: '🎮',
844
+ wechat: '💬',
845
+ qq: '🐧'
846
+ };
847
+
848
+ // 提供商品牌色
849
+ const providerColors = {
850
+ google: '#4285f4',
851
+ apple: '#000000',
852
+ github: '#24292e',
853
+ twitter: '#1da1f2',
854
+ facebook: '#1877f2',
855
+ discord: '#5865f2',
856
+ wechat: '#07c160',
857
+ qq: '#12b7f5'
858
+ };
859
+
860
+ /**
861
+ * 第三方登录按钮
862
+ */
863
+ function OAuthButton({
864
+ provider,
865
+ variant = 'default',
866
+ onSuccess,
867
+ onError
868
+ }) {
869
+ const {
870
+ login,
871
+ loading
872
+ } = useOAuthLogin();
873
+ const handleClick = useCallback(async () => {
874
+ try {
875
+ const result = await login(provider, {
876
+ popup: true
877
+ });
878
+ onSuccess?.(result);
879
+ } catch (err) {
880
+ onError?.(err);
881
+ }
882
+ }, [login, provider, onSuccess, onError]);
883
+ const icon = providerIcons[provider] || '🔐';
884
+ const color = providerColors[provider];
885
+ return /*#__PURE__*/React.createElement("button", {
886
+ className: `eco-oauth-btn eco-oauth-btn-${variant} eco-oauth-btn-${provider}`,
887
+ onClick: handleClick,
888
+ disabled: loading,
889
+ style: variant === 'brand' ? {
890
+ backgroundColor: color
891
+ } : undefined
892
+ }, /*#__PURE__*/React.createElement("span", {
893
+ className: "eco-oauth-btn-icon"
894
+ }, icon), /*#__PURE__*/React.createElement("span", {
895
+ className: "eco-oauth-btn-text"
896
+ }, loading ? t('loading') : t('loginWith', {
897
+ provider: t(provider)
898
+ })));
899
+ }
900
+
901
+ /**
902
+ * 登录按钮组
903
+ */
904
+ function OAuthButtonGroup({
905
+ providers = ['google', 'github', 'twitter'],
906
+ variant = 'default',
907
+ onSuccess,
908
+ onError
909
+ }) {
910
+ return /*#__PURE__*/React.createElement("div", {
911
+ className: "eco-oauth-btn-group"
912
+ }, providers.map(provider => /*#__PURE__*/React.createElement(OAuthButton, {
913
+ key: provider,
914
+ provider: provider,
915
+ variant: variant,
916
+ onSuccess: onSuccess,
917
+ onError: onError
918
+ })));
919
+ }
920
+
921
+ /**
922
+ * 图标按钮组
923
+ */
924
+ function OAuthIconGroup({
925
+ providers = ['google', 'github', 'twitter'],
926
+ onSuccess,
927
+ onError
928
+ }) {
929
+ const {
930
+ login,
931
+ loading
932
+ } = useOAuthLogin();
933
+ const handleClick = useCallback(async provider => {
934
+ try {
935
+ const result = await login(provider, {
936
+ popup: true
937
+ });
938
+ onSuccess?.(result);
939
+ } catch (err) {
940
+ onError?.(err);
941
+ }
942
+ }, [login, onSuccess, onError]);
943
+ return /*#__PURE__*/React.createElement("div", {
944
+ className: "eco-oauth-icon-group"
945
+ }, providers.map(provider => /*#__PURE__*/React.createElement("button", {
946
+ key: provider,
947
+ className: `eco-oauth-icon-btn eco-oauth-icon-${provider}`,
948
+ onClick: () => handleClick(provider),
949
+ disabled: loading,
950
+ title: t(provider),
951
+ style: {
952
+ backgroundColor: providerColors[provider]
953
+ }
954
+ }, providerIcons[provider] || '🔐')));
955
+ }
956
+
957
+ /**
958
+ * OAuth回调页面
959
+ */
960
+ function OAuthCallbackPage({
961
+ onSuccess,
962
+ onError
963
+ }) {
964
+ const {
965
+ status,
966
+ user,
967
+ error
968
+ } = useOAuthCallback();
969
+ if (status === 'loading') {
970
+ return /*#__PURE__*/React.createElement("div", {
971
+ className: "eco-oauth-callback"
972
+ }, /*#__PURE__*/React.createElement("div", {
973
+ className: "eco-oauth-callback-loading"
974
+ }, /*#__PURE__*/React.createElement("div", {
975
+ className: "eco-oauth-spinner"
976
+ }), /*#__PURE__*/React.createElement("span", null, t('authorizing'))));
977
+ }
978
+ if (status === 'error') {
979
+ onError?.(error);
980
+ return /*#__PURE__*/React.createElement("div", {
981
+ className: "eco-oauth-callback"
982
+ }, /*#__PURE__*/React.createElement("div", {
983
+ className: "eco-oauth-callback-error"
984
+ }, /*#__PURE__*/React.createElement("span", {
985
+ className: "eco-oauth-callback-icon"
986
+ }, "\u274C"), /*#__PURE__*/React.createElement("h3", null, t('failed')), /*#__PURE__*/React.createElement("p", null, error), /*#__PURE__*/React.createElement("button", {
987
+ onClick: () => window.close()
988
+ }, t('back'))));
989
+ }
990
+ if (status === 'success') {
991
+ onSuccess?.(user);
992
+
993
+ // 如果在弹窗中,发送消息给父窗口
994
+ if (window.opener) {
995
+ window.opener.postMessage({
996
+ type: 'oauth_callback',
997
+ result: {
998
+ user
999
+ }
1000
+ }, window.location.origin);
1001
+ }
1002
+ return /*#__PURE__*/React.createElement("div", {
1003
+ className: "eco-oauth-callback"
1004
+ }, /*#__PURE__*/React.createElement("div", {
1005
+ className: "eco-oauth-callback-success"
1006
+ }, /*#__PURE__*/React.createElement("span", {
1007
+ className: "eco-oauth-callback-icon"
1008
+ }, "\u2705"), /*#__PURE__*/React.createElement("h3", null, t('success')), /*#__PURE__*/React.createElement("p", null, t('continueAs', {
1009
+ name: user?.name
1010
+ }))));
1011
+ }
1012
+ return null;
1013
+ }
1014
+
1015
+ /**
1016
+ * 已关联账户列表
1017
+ */
1018
+ function LinkedAccountsList() {
1019
+ const {
1020
+ accounts,
1021
+ loading,
1022
+ unlinkAccount,
1023
+ isLinked
1024
+ } = useLinkedAccounts();
1025
+ const {
1026
+ providers
1027
+ } = useOAuthProviders();
1028
+ if (loading) {
1029
+ return /*#__PURE__*/React.createElement("div", {
1030
+ className: "eco-oauth-linked eco-oauth-loading"
1031
+ }, /*#__PURE__*/React.createElement("div", {
1032
+ className: "eco-oauth-spinner"
1033
+ }));
1034
+ }
1035
+ return /*#__PURE__*/React.createElement("div", {
1036
+ className: "eco-oauth-linked"
1037
+ }, /*#__PURE__*/React.createElement("h4", null, t('linkedAccounts')), accounts.length === 0 ? /*#__PURE__*/React.createElement("div", {
1038
+ className: "eco-oauth-linked-empty"
1039
+ }, t('noLinkedAccounts')) : /*#__PURE__*/React.createElement("div", {
1040
+ className: "eco-oauth-linked-list"
1041
+ }, accounts.map(account => /*#__PURE__*/React.createElement("div", {
1042
+ key: account.provider,
1043
+ className: "eco-oauth-linked-item"
1044
+ }, /*#__PURE__*/React.createElement("span", {
1045
+ className: "eco-oauth-linked-icon",
1046
+ style: {
1047
+ backgroundColor: providerColors[account.provider]
1048
+ }
1049
+ }, providerIcons[account.provider]), /*#__PURE__*/React.createElement("div", {
1050
+ className: "eco-oauth-linked-info"
1051
+ }, /*#__PURE__*/React.createElement("span", {
1052
+ className: "eco-oauth-linked-name"
1053
+ }, t(account.provider)), /*#__PURE__*/React.createElement("span", {
1054
+ className: "eco-oauth-linked-email"
1055
+ }, account.email)), /*#__PURE__*/React.createElement("button", {
1056
+ className: "eco-oauth-unlink-btn",
1057
+ onClick: () => unlinkAccount(account.provider)
1058
+ }, t('unlinkAccount'))))), /*#__PURE__*/React.createElement("div", {
1059
+ className: "eco-oauth-link-more"
1060
+ }, /*#__PURE__*/React.createElement("h5", null, t('linkAccount')), /*#__PURE__*/React.createElement("div", {
1061
+ className: "eco-oauth-link-options"
1062
+ }, providers.filter(p => !isLinked(p.id)).map(p => /*#__PURE__*/React.createElement(OAuthButton, {
1063
+ key: p.id,
1064
+ provider: p.id,
1065
+ variant: "outline"
1066
+ })))));
1067
+ }
1068
+
1069
+ /**
1070
+ * 登录模态框
1071
+ */
1072
+ function OAuthLoginModal({
1073
+ isOpen,
1074
+ onClose,
1075
+ onSuccess
1076
+ }) {
1077
+ if (!isOpen) return null;
1078
+ return /*#__PURE__*/React.createElement("div", {
1079
+ className: "eco-oauth-modal"
1080
+ }, /*#__PURE__*/React.createElement("div", {
1081
+ className: "eco-oauth-modal-overlay",
1082
+ onClick: onClose
1083
+ }), /*#__PURE__*/React.createElement("div", {
1084
+ className: "eco-oauth-modal-content"
1085
+ }, /*#__PURE__*/React.createElement("button", {
1086
+ className: "eco-oauth-modal-close",
1087
+ onClick: onClose
1088
+ }, "\xD7"), /*#__PURE__*/React.createElement("h2", null, t('login')), /*#__PURE__*/React.createElement(OAuthButtonGroup, {
1089
+ providers: ['google', 'github', 'twitter'],
1090
+ variant: "brand",
1091
+ onSuccess: result => {
1092
+ onSuccess?.(result);
1093
+ onClose();
1094
+ }
1095
+ }), /*#__PURE__*/React.createElement("div", {
1096
+ className: "eco-oauth-modal-divider"
1097
+ }, /*#__PURE__*/React.createElement("span", null, "\u6216")), /*#__PURE__*/React.createElement(OAuthIconGroup, {
1098
+ providers: ['facebook', 'discord', 'apple'],
1099
+ onSuccess: result => {
1100
+ onSuccess?.(result);
1101
+ onClose();
1102
+ }
1103
+ }), /*#__PURE__*/React.createElement("div", {
1104
+ className: "eco-oauth-modal-footer"
1105
+ }, /*#__PURE__*/React.createElement("span", null, t('byLoggingIn')), /*#__PURE__*/React.createElement("a", {
1106
+ href: "/terms"
1107
+ }, t('termsOfService')), /*#__PURE__*/React.createElement("span", null, t('and')), /*#__PURE__*/React.createElement("a", {
1108
+ href: "/privacy"
1109
+ }, t('privacyPolicy')))));
1110
+ }
1111
+
1112
+ /**
1113
+ * @quantabit/oauth-sdk
1114
+ * OAuth System SDK - Full Version
1115
+ */
1116
+
1117
+ const getAuthUrl = (...args) => oauthApi.getAuthUrl(...args);
1118
+ const handleCallback = (...args) => oauthApi.handleCallback(...args);
1119
+ const checkAuth = (...args) => oauthApi.checkAuth(...args);
1120
+ const logout = (...args) => oauthApi.logout(...args);
1121
+ const getLinkedAccounts = (...args) => oauthApi.getLinkedAccounts(...args);
1122
+
1123
+ export { BindingStatus, ConnectionStatus, LinkedAccountsList, OAuthApiClient, OAuthButton, OAuthButtonGroup, OAuthCallbackPage, OAuthIconGroup, OAuthLoginModal, OAuthProvider, OAuthScope, SUPPORTED_LANGUAGES, checkAuth, getAuthUrl, getLanguage, getLinkedAccounts, handleCallback, logout, messages, oauthApi, setLanguage, t, useLinkedAccounts, useOAuthCallback, useOAuthLogin, useOAuthProviders, useOAuthState };
1124
+ //# sourceMappingURL=index.esm.js.map