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