@arcblock/did-connect-react 3.4.15 → 3.5.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.
Files changed (198) hide show
  1. package/dist/standalone/did-connect-react.css +1 -0
  2. package/dist/standalone/index.js +133700 -0
  3. package/lib/package.json.js +1 -1
  4. package/package.json +11 -6
  5. package/.aigne/doc-smith/config.yaml +0 -85
  6. package/.aigne/doc-smith/history.yaml +0 -6
  7. package/.aigne/doc-smith/output/structure-plan.json +0 -204
  8. package/.aigne/doc-smith/translation-cache.yaml +0 -11
  9. package/.aigne/doc-smith/upload-cache.yaml +0 -213
  10. package/docs/_sidebar.md +0 -18
  11. package/docs/advanced-authentication-methods.ja.md +0 -261
  12. package/docs/advanced-authentication-methods.md +0 -261
  13. package/docs/advanced-authentication-methods.zh-TW.md +0 -261
  14. package/docs/advanced-authentication-methods.zh.md +0 -261
  15. package/docs/advanced-utilities.ja.md +0 -132
  16. package/docs/advanced-utilities.md +0 -132
  17. package/docs/advanced-utilities.zh-TW.md +0 -132
  18. package/docs/advanced-utilities.zh.md +0 -132
  19. package/docs/advanced.ja.md +0 -95
  20. package/docs/advanced.md +0 -95
  21. package/docs/advanced.zh-TW.md +0 -95
  22. package/docs/advanced.zh.md +0 -95
  23. package/docs/api-reference.ja.md +0 -178
  24. package/docs/api-reference.md +0 -178
  25. package/docs/api-reference.zh-TW.md +0 -178
  26. package/docs/api-reference.zh.md +0 -178
  27. package/docs/assets/diagram/core-components-session-provider-01.ja.jpg +0 -0
  28. package/docs/assets/diagram/core-components-session-provider-01.jpg +0 -0
  29. package/docs/assets/diagram/core-components-session-provider-01.zh-TW.jpg +0 -0
  30. package/docs/assets/diagram/core-components-session-provider-01.zh.jpg +0 -0
  31. package/docs/assets/diagram/did-connect-diagram-0.ja.jpg +0 -0
  32. package/docs/assets/diagram/did-connect-diagram-0.jpg +0 -0
  33. package/docs/assets/diagram/did-connect-diagram-0.zh-TW.jpg +0 -0
  34. package/docs/assets/diagram/did-connect-diagram-0.zh.jpg +0 -0
  35. package/docs/assets/diagram/overview-01.ja.jpg +0 -0
  36. package/docs/assets/diagram/overview-01.jpg +0 -0
  37. package/docs/assets/diagram/overview-01.zh-TW.jpg +0 -0
  38. package/docs/assets/diagram/overview-01.zh.jpg +0 -0
  39. package/docs/assets/diagram/use-connect-diagram-0.ja.jpg +0 -0
  40. package/docs/assets/diagram/use-connect-diagram-0.jpg +0 -0
  41. package/docs/assets/diagram/use-connect-diagram-0.zh-TW.jpg +0 -0
  42. package/docs/assets/diagram/use-connect-diagram-0.zh.jpg +0 -0
  43. package/docs/core-components-did-connect.ja.md +0 -166
  44. package/docs/core-components-did-connect.md +0 -166
  45. package/docs/core-components-did-connect.zh-TW.md +0 -166
  46. package/docs/core-components-did-connect.zh.md +0 -166
  47. package/docs/core-components-session-provider.ja.md +0 -197
  48. package/docs/core-components-session-provider.md +0 -197
  49. package/docs/core-components-session-provider.zh-TW.md +0 -197
  50. package/docs/core-components-session-provider.zh.md +0 -197
  51. package/docs/core-components.ja.md +0 -16
  52. package/docs/core-components.md +0 -16
  53. package/docs/core-components.zh-TW.md +0 -16
  54. package/docs/core-components.zh.md +0 -16
  55. package/docs/getting-started.ja.md +0 -138
  56. package/docs/getting-started.md +0 -138
  57. package/docs/getting-started.zh-TW.md +0 -138
  58. package/docs/getting-started.zh.md +0 -138
  59. package/docs/hooks-use-connect.ja.md +0 -178
  60. package/docs/hooks-use-connect.md +0 -178
  61. package/docs/hooks-use-connect.zh-TW.md +0 -178
  62. package/docs/hooks-use-connect.zh.md +0 -178
  63. package/docs/hooks-use-did.ja.md +0 -107
  64. package/docs/hooks-use-did.md +0 -107
  65. package/docs/hooks-use-did.zh-TW.md +0 -107
  66. package/docs/hooks-use-did.zh.md +0 -107
  67. package/docs/hooks-use-oauth-passkey.ja.md +0 -188
  68. package/docs/hooks-use-oauth-passkey.md +0 -188
  69. package/docs/hooks-use-oauth-passkey.zh-TW.md +0 -188
  70. package/docs/hooks-use-oauth-passkey.zh.md +0 -188
  71. package/docs/hooks.ja.md +0 -23
  72. package/docs/hooks.md +0 -23
  73. package/docs/hooks.zh-TW.md +0 -23
  74. package/docs/hooks.zh.md +0 -23
  75. package/docs/overview.ja.md +0 -119
  76. package/docs/overview.md +0 -119
  77. package/docs/overview.zh-TW.md +0 -119
  78. package/docs/overview.zh.md +0 -119
  79. package/docs/ui-components-address.ja.md +0 -121
  80. package/docs/ui-components-address.md +0 -121
  81. package/docs/ui-components-address.zh-TW.md +0 -121
  82. package/docs/ui-components-address.zh.md +0 -121
  83. package/docs/ui-components-avatar.ja.md +0 -65
  84. package/docs/ui-components-avatar.md +0 -65
  85. package/docs/ui-components-avatar.zh-TW.md +0 -65
  86. package/docs/ui-components-avatar.zh.md +0 -65
  87. package/docs/ui-components-button.ja.md +0 -99
  88. package/docs/ui-components-button.md +0 -99
  89. package/docs/ui-components-button.zh-TW.md +0 -99
  90. package/docs/ui-components-button.zh.md +0 -99
  91. package/docs/ui-components-logo.ja.md +0 -52
  92. package/docs/ui-components-logo.md +0 -52
  93. package/docs/ui-components-logo.zh-TW.md +0 -52
  94. package/docs/ui-components-logo.zh.md +0 -52
  95. package/docs/ui-components.ja.md +0 -57
  96. package/docs/ui-components.md +0 -57
  97. package/docs/ui-components.zh-TW.md +0 -57
  98. package/docs/ui-components.zh.md +0 -57
  99. package/glossary.md +0 -1
  100. package/src/Address/index.jsx +0 -2
  101. package/src/Avatar/index.jsx +0 -2
  102. package/src/Button/Button.stories.jsx +0 -7
  103. package/src/Button/index.jsx +0 -21
  104. package/src/Connect/Connect.stories.jsx +0 -34
  105. package/src/Connect/assets/locale.js +0 -149
  106. package/src/Connect/assets/login-bg.png +0 -0
  107. package/src/Connect/assets/login-slogan.js +0 -7
  108. package/src/Connect/components/action-button.jsx +0 -22
  109. package/src/Connect/components/app-tips.jsx +0 -156
  110. package/src/Connect/components/auto-height.jsx +0 -38
  111. package/src/Connect/components/back-button.jsx +0 -24
  112. package/src/Connect/components/connect-status.jsx +0 -259
  113. package/src/Connect/components/did-connect-title.jsx +0 -107
  114. package/src/Connect/components/download-tips.jsx +0 -55
  115. package/src/Connect/components/loading.jsx +0 -25
  116. package/src/Connect/components/login-item/connect-choose-list.jsx +0 -328
  117. package/src/Connect/components/login-item/connect-provider-list.jsx +0 -473
  118. package/src/Connect/components/login-item/login-method-item.jsx +0 -139
  119. package/src/Connect/components/login-item/mobile-login-item.jsx +0 -184
  120. package/src/Connect/components/login-item/passkey-login-item.jsx +0 -56
  121. package/src/Connect/components/login-item/wallet-login-options.jsx +0 -129
  122. package/src/Connect/components/login-item/web-login-item.jsx +0 -159
  123. package/src/Connect/components/mask-overlay.jsx +0 -32
  124. package/src/Connect/components/refresh-overlay.jsx +0 -52
  125. package/src/Connect/components/switch-app.jsx +0 -69
  126. package/src/Connect/connect.jsx +0 -635
  127. package/src/Connect/contexts/state.jsx +0 -235
  128. package/src/Connect/fallback-connect.jsx +0 -47
  129. package/src/Connect/fullpage.jsx +0 -3
  130. package/src/Connect/hooks/auth-url.js +0 -31
  131. package/src/Connect/hooks/method-list.js +0 -121
  132. package/src/Connect/hooks/page-show.js +0 -24
  133. package/src/Connect/hooks/provider-list.js +0 -168
  134. package/src/Connect/hooks/security.js +0 -40
  135. package/src/Connect/hooks/token.js +0 -627
  136. package/src/Connect/hooks/use-apps.js +0 -69
  137. package/src/Connect/hooks/use-quick-connect.js +0 -119
  138. package/src/Connect/index.jsx +0 -21
  139. package/src/Connect/landing-page.jsx +0 -3
  140. package/src/Connect/plugins/email/index.jsx +0 -85
  141. package/src/Connect/plugins/email/list-item.jsx +0 -35
  142. package/src/Connect/plugins/email/placeholder.jsx +0 -372
  143. package/src/Connect/plugins/index.js +0 -2
  144. package/src/Connect/use-connect.jsx +0 -321
  145. package/src/Connect/with-blocklet.jsx +0 -26
  146. package/src/Connect/with-bridge-call.jsx +0 -138
  147. package/src/Federated/context.jsx +0 -93
  148. package/src/Federated/index.jsx +0 -1
  149. package/src/Logo/index.jsx +0 -2
  150. package/src/OAuth/bind-conflict-alert.jsx +0 -37
  151. package/src/OAuth/context.jsx +0 -407
  152. package/src/OAuth/guest.svg +0 -20
  153. package/src/OAuth/index.jsx +0 -1
  154. package/src/OAuth/passport-switcher.jsx +0 -2
  155. package/src/Passkey/actions.jsx +0 -217
  156. package/src/Passkey/constants.js +0 -2
  157. package/src/Passkey/context.jsx +0 -395
  158. package/src/Passkey/dialog.jsx +0 -401
  159. package/src/Passkey/icon.jsx +0 -10
  160. package/src/Passkey/index.jsx +0 -2
  161. package/src/Service/index.jsx +0 -96
  162. package/src/Session/assets/did-spaces-guide-cover.svg +0 -1
  163. package/src/Session/assets/did-spaces-guide-icon.svg +0 -7
  164. package/src/Session/context.jsx +0 -7
  165. package/src/Session/did-spaces-guide.jsx +0 -173
  166. package/src/Session/handler.jsx +0 -98
  167. package/src/Session/hooks/use-federated.js +0 -91
  168. package/src/Session/hooks/use-mobile.jsx +0 -6
  169. package/src/Session/hooks/use-protected-routes.js +0 -16
  170. package/src/Session/hooks/use-session-token.js +0 -400
  171. package/src/Session/hooks/use-verify.jsx +0 -76
  172. package/src/Session/index.jsx +0 -1789
  173. package/src/Session/libs/constants.js +0 -17
  174. package/src/Session/libs/did-spaces.js +0 -38
  175. package/src/Session/libs/federated.js +0 -82
  176. package/src/Session/libs/index.js +0 -5
  177. package/src/Session/libs/locales.js +0 -160
  178. package/src/Session/libs/login-mobile.js +0 -80
  179. package/src/Session/window-focus-aware.jsx +0 -28
  180. package/src/SessionManager/index.jsx +0 -2
  181. package/src/Storage/engine/cookie.js +0 -25
  182. package/src/Storage/engine/local-storage.js +0 -57
  183. package/src/Storage/index.js +0 -25
  184. package/src/User/index.js +0 -4
  185. package/src/User/use-did.js +0 -80
  186. package/src/User/wrap-did.jsx +0 -18
  187. package/src/WebWalletSWKeeper/index.jsx +0 -3
  188. package/src/components/PassportSwitcher.jsx +0 -160
  189. package/src/constant.js +0 -27
  190. package/src/error.js +0 -6
  191. package/src/hooks/use-locale.jsx +0 -6
  192. package/src/index.js +0 -32
  193. package/src/locales/en.jsx +0 -15
  194. package/src/locales/index.jsx +0 -13
  195. package/src/locales/zh.jsx +0 -15
  196. package/src/types.d.ts +0 -355
  197. package/src/utils.js +0 -413
  198. package/vite.config.mjs +0 -29
@@ -1,119 +0,0 @@
1
- import { useCreation, useMemoizedFn, useReactive, useRequest } from 'ahooks';
2
- import { joinURL } from 'ufo';
3
- import pick from 'lodash/pick';
4
- import unionBy from 'lodash/unionBy';
5
- import sortBy from 'lodash/sortBy';
6
- import Toast from '@arcblock/ux/lib/Toast';
7
-
8
- import { BLOCKLET_SERVICE_PATH_PREFIX } from '../../constant';
9
- import { createAxios, logger } from '../../utils';
10
-
11
- const prefix = joinURL(BLOCKLET_SERVICE_PATH_PREFIX, '/api/user-session');
12
-
13
- export default function useQuickConnect({
14
- appPid,
15
- sourceAppPid,
16
- loginAppPid,
17
- autoFetch = true,
18
- baseUrl = '/',
19
- fetchAll = false,
20
- } = {}) {
21
- const api = useCreation(() => {
22
- return createAxios(
23
- {
24
- baseURL: joinURL(baseUrl, prefix),
25
- },
26
- { lazy: true, lazyTime: 500 }
27
- );
28
- }, [baseUrl]);
29
-
30
- const getUserSessions = useMemoizedFn(async () => {
31
- if (!window.blocklet) {
32
- return [];
33
- }
34
-
35
- let userSessions = [];
36
- try {
37
- ({ data: userSessions = [] } = await api.get(''));
38
- } catch (err) {
39
- logger.error('Failed to get user-sessions', err);
40
- }
41
- // NOTICE: 已经包含了 federated master userSessions
42
- const result = userSessions
43
- .filter((x) => {
44
- if (!fetchAll) {
45
- return !x.appUrl;
46
- }
47
- return true;
48
- })
49
- .map((x) => ({
50
- appUrl: baseUrl,
51
- ...x,
52
- }));
53
- return result;
54
- });
55
- const currentState = useReactive({
56
- loaded: false,
57
- loadingId: null,
58
- });
59
- const userSessionsState = useRequest(getUserSessions, {
60
- manual: !autoFetch,
61
- onFinally() {
62
- currentState.loaded = true;
63
- },
64
- refreshDeps: [baseUrl],
65
- });
66
-
67
- const loginUserSession = async (userSession) => {
68
- const params = pick(userSession, ['userDid', 'visitorId', 'passportId', 'id']);
69
- try {
70
- currentState.loadingId = userSession.id;
71
- const { data } = await api.post(
72
- 'login',
73
- { ...params, appPid: loginAppPid },
74
- {
75
- baseURL: joinURL(userSession.appUrl || '/', prefix),
76
- }
77
- );
78
- return data;
79
- } catch (err) {
80
- const errorMsg = err.response ? err.response?.data?.error || err.response?.data : err.message;
81
- Toast.error(errorMsg);
82
- logger.error('Quick login error', err);
83
- throw err;
84
- } finally {
85
- currentState.loadingId = null;
86
- }
87
- };
88
-
89
- const onlineUserSessions = useCreation(() => {
90
- if (!userSessionsState.data) return [];
91
- return userSessionsState.data.filter((x) => x.status !== 'expired');
92
- }, [userSessionsState.data]);
93
-
94
- const uniqueUserSessions = useCreation(() => {
95
- const now = new Date().getTime();
96
- const sortedUserSessions = sortBy(onlineUserSessions, (x) => now - new Date(x.updatedAt).getTime());
97
- return unionBy(sortedUserSessions, (x) =>
98
- [
99
- x.userDid,
100
- // 暂不以用户角色来做去重的因子
101
- // x.user.role
102
- ].join('_')
103
- );
104
- }, [onlineUserSessions, sourceAppPid, appPid]);
105
-
106
- const quickLoginEnabled = useCreation(() => {
107
- return Boolean(uniqueUserSessions && uniqueUserSessions.length > 0);
108
- }, [uniqueUserSessions]);
109
-
110
- return {
111
- loadingId: currentState.loadingId,
112
- userSessions: uniqueUserSessions,
113
- quickLoginEnabled,
114
- loginUserSession,
115
- loading: userSessionsState.loading,
116
- loaded: userSessionsState.loaded,
117
- refresh: userSessionsState.refreshAsync,
118
- };
119
- }
@@ -1,21 +0,0 @@
1
- import { lazy, Suspense } from 'react';
2
- import { withContainer, withUxTheme } from '@arcblock/ux/lib/DIDConnect';
3
-
4
- import withBlocklet from './with-blocklet';
5
- import withBridgeCall from './with-bridge-call';
6
- import useSecurity from './hooks/security';
7
- import FallbackConnect from './fallback-connect';
8
-
9
- const LazyConnect = lazy(() => import('./connect'));
10
-
11
- function WrapConnect(props) {
12
- return (
13
- <Suspense fallback={<FallbackConnect />}>
14
- <LazyConnect {...props} />
15
- </Suspense>
16
- );
17
- }
18
-
19
- export default withUxTheme(withBridgeCall(withBlocklet(withContainer(WrapConnect))));
20
-
21
- export { useSecurity };
@@ -1,3 +0,0 @@
1
- import LandingPage from '@arcblock/ux/lib/DIDConnect/landing-page';
2
-
3
- export default LandingPage;
@@ -1,85 +0,0 @@
1
- import { useMemoizedFn, useReactive } from 'ahooks';
2
- import mailOutlineRoundedIcon from '@iconify-icons/material-symbols/mail-outline-rounded';
3
- import { LOGIN_PROVIDER } from '@arcblock/ux/lib/Util/constant';
4
- import { translate } from '@arcblock/ux/lib/Locale/util';
5
-
6
- import EmailListItem from './list-item';
7
- import EmailPlaceholder from './placeholder';
8
- import defaultTranslations from '../../assets/locale';
9
-
10
- export default function useEmailPlugin({ baseUrl }) {
11
- const state = useReactive({
12
- baseUrl,
13
- status: 'idle',
14
- get computedStatus() {
15
- if (this.status === 'idle') {
16
- return 'creating';
17
- }
18
- if (this.status === 'creating') {
19
- return 'scanned';
20
- }
21
- if (this.status === 'sending') {
22
- return 'scanned';
23
- }
24
- if (this.status === 'verifying') {
25
- return 'scanned';
26
- }
27
- return this.status;
28
- },
29
- error: '',
30
- magicToken: '',
31
- reset() {
32
- this.status = 'idle';
33
- this.error = '';
34
- this.magicToken = '';
35
- },
36
- });
37
- const name = LOGIN_PROVIDER.EMAIL;
38
- const title = 'Email';
39
- const icon = mailOutlineRoundedIcon;
40
- const translations = {
41
- zh: {
42
- ...defaultTranslations.zh,
43
- email: '邮箱地址',
44
- emailPlaceholder: '请输入邮箱地址',
45
- sendCode: '发送验证邮件',
46
- confirm: '确认',
47
- verifyEmail: '登录链接已发送至邮箱,请登录邮箱查看',
48
- useCode: '使用验证码',
49
- codePlaceholder: '请输入验证码',
50
- orUseCodePlaceholder: '或使用验证码',
51
- emailInvalid: '邮箱格式不正确',
52
- emailRequired: '邮箱地址不能为空',
53
- },
54
- en: {
55
- ...defaultTranslations.en,
56
- email: 'Email',
57
- emailPlaceholder: 'Enter your email address',
58
- sendCode: 'Send Verification Email',
59
- confirm: 'Confirm',
60
- verifyEmail: 'Login link sent! Please check your email.',
61
- useCode: 'Use Verification Code',
62
- codePlaceholder: 'Enter your verification code.',
63
- orUseCodePlaceholder: 'Or use verification code.',
64
- emailInvalid: 'Email format is incorrect',
65
- emailRequired: 'Email is required',
66
- },
67
- };
68
- const t = useMemoizedFn((key, data = {}, locale = 'en') => {
69
- return translate(translations, key, locale, 'en', data);
70
- });
71
-
72
- return {
73
- name,
74
- title,
75
- icon,
76
- renderListItem(props) {
77
- return <EmailListItem {...props} state={state} name={name} title={title} icon={icon} t={t} />;
78
- },
79
- renderPlaceholder(props) {
80
- return <EmailPlaceholder {...props} state={state} name={name} title={title} icon={icon} t={t} />;
81
- },
82
- order: 100,
83
- state,
84
- };
85
- }
@@ -1,35 +0,0 @@
1
- import PropTypes from 'prop-types';
2
- import { useMemoizedFn } from 'ahooks';
3
- import mailFilledIcon from '@iconify-icons/tabler/mail-filled';
4
- import { LOGIN_PROVIDER, LOGIN_PROVIDER_NAME } from '@arcblock/ux/lib/Util/constant';
5
- import { GA_LAST_LOGIN_METHOD } from '@arcblock/ux/lib/withTracker/constant';
6
-
7
- import LoginMethodItem from '../../components/login-item/login-method-item';
8
- import { useStateContext } from '../../contexts/state';
9
-
10
- export default function EmailListItem({ ...rest }) {
11
- const { setSelectedPlugin, getPlugin, connectState, lastLoginMethod } = useStateContext();
12
- const handleConnect = useMemoizedFn(() => {
13
- localStorage.setItem(GA_LAST_LOGIN_METHOD, LOGIN_PROVIDER.EMAIL);
14
- const plugin = getPlugin(LOGIN_PROVIDER.EMAIL);
15
- plugin.state.reset();
16
- plugin.state.status = 'creating';
17
- connectState.chooseMethod = LOGIN_PROVIDER.EMAIL;
18
- connectState.walletMethod = ''; // 清除 wallet 登录方式,防止状态污染
19
- setSelectedPlugin(plugin);
20
- });
21
-
22
- return (
23
- <LoginMethodItem
24
- {...rest}
25
- isLatest={lastLoginMethod && lastLoginMethod === LOGIN_PROVIDER.EMAIL}
26
- title={LOGIN_PROVIDER_NAME[LOGIN_PROVIDER.EMAIL]}
27
- icon={mailFilledIcon}
28
- onClick={handleConnect}
29
- />
30
- );
31
- }
32
-
33
- EmailListItem.propTypes = {
34
- onClick: PropTypes.func,
35
- };
@@ -1,372 +0,0 @@
1
- import LoadingMask from '@arcblock/ux/lib/LoadingMask';
2
- import { Box, CircularProgress, TextField, Typography } from '@mui/material';
3
- import { useCreation, useInterval, useMemoizedFn, useReactive } from 'ahooks';
4
- import PropTypes from 'prop-types';
5
- import { joinURL, withQuery } from 'ufo';
6
- import Toast from '@arcblock/ux/lib/Toast';
7
- import VerificationCode from '@arcblock/ux/lib/VerificationCode';
8
- import { useEffect } from 'react';
9
- import noop from 'lodash/noop';
10
- import ProviderIcon from '@arcblock/ux/lib/DIDConnect/provider-icon';
11
- import { LOGIN_PROVIDER, LAST_USED_LOGIN_METHOD } from '@arcblock/ux/lib/Util/constant';
12
-
13
- import BackButton from '../../components/back-button';
14
- import ActionButton from '../../components/action-button';
15
- import { useStateContext } from '../../contexts/state';
16
- import { BLOCKLET_SERVICE_PATH_PREFIX, CHECK_STATUS_INTERVAL, VERIFY_CODE_LENGTH } from '../../../constant';
17
- import { createAxios, debug } from '../../../utils';
18
-
19
- export default function EmailPlaceholder({
20
- fallback = null,
21
- state,
22
- forceUpdate = noop,
23
- onSuccess = noop,
24
- t: rawT = noop,
25
- }) {
26
- const { setSelectedPlugin, locale, connectState, action, extraParams } = useStateContext();
27
- const t = useMemoizedFn((key, data = {}) => {
28
- return rawT(key, data, locale);
29
- });
30
- const api = useCreation(() => {
31
- return createAxios({
32
- baseURL: joinURL(state.baseUrl, BLOCKLET_SERVICE_PATH_PREFIX, '/api/user'),
33
- });
34
- }, [state.baseUrl]);
35
-
36
- const currentState = useReactive({
37
- email: '',
38
- code: '',
39
- codeId: '',
40
- defaultVerifyMode: 'magicLink',
41
- verifyMode: 'magicLink', // magicLink, code
42
- loadingSendCode: false,
43
- loadingVerifyCode: false,
44
- error: '',
45
- });
46
-
47
- const reset = useMemoizedFn(() => {
48
- currentState.email = '';
49
- currentState.code = '';
50
- currentState.codeId = '';
51
- currentState.verifyMode = currentState.defaultVerifyMode;
52
- currentState.loadingSendCode = false;
53
- currentState.loadingVerifyCode = false;
54
- currentState.error = '';
55
- });
56
-
57
- const sendCode = useMemoizedFn(async () => {
58
- if (!currentState.email || !currentState.email.trim()) {
59
- currentState.error = t('emailRequired');
60
- return;
61
- }
62
-
63
- // 检查邮箱格式
64
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
65
- if (!emailRegex.test(currentState.email)) {
66
- currentState.error = t('emailInvalid');
67
- return;
68
- }
69
- currentState.error = '';
70
-
71
- try {
72
- currentState.loadingSendCode = true;
73
- let useMagicLink = false;
74
- // FIXME: @zhanghan 暂时只允许登录时使用 magicLink,否则其他的事件无法调用正确的回调
75
- if (['login'].includes(action)) {
76
- // FIXME: @zhanghan 如果 sourceAppPid 存在,则不使用 magicLink,暂无法获得正确的 magicLink 地址
77
- if (!connectState.sourceAppPid) {
78
- useMagicLink = true;
79
- }
80
- }
81
- const { data } = await api.post(withQuery('/email/sendCode', { locale }), {
82
- email: currentState.email,
83
- sourceAppPid: connectState.sourceAppPid,
84
- useMagicLink,
85
- });
86
- currentState.verifyMode = currentState.defaultVerifyMode;
87
- currentState.codeId = data.id;
88
- state.status = 'sending';
89
- } catch (error) {
90
- const errorMessage = error?.response?.data?.error || error?.message;
91
- state.status = 'error';
92
- state.error = errorMessage;
93
- } finally {
94
- currentState.loadingSendCode = false;
95
- forceUpdate();
96
- }
97
- });
98
- const verifyCode = useMemoizedFn(async () => {
99
- if (currentState.code.length !== VERIFY_CODE_LENGTH) {
100
- return;
101
- }
102
-
103
- try {
104
- currentState.loadingVerifyCode = true;
105
- const { data: loginResult } = await api.post(withQuery('/email/login', { locale }), {
106
- ...extraParams,
107
- action,
108
- code: currentState.code,
109
- sourceAppPid: connectState.sourceAppPid,
110
- });
111
- debug('Email login succeed (use code)', { loginResult });
112
- // 登录成功后记录最后使用的登录方式(用于显示 Last Used 标签)
113
- localStorage.setItem(LAST_USED_LOGIN_METHOD, LOGIN_PROVIDER.EMAIL);
114
- await onSuccess(
115
- {
116
- ...loginResult,
117
- encrypted: false,
118
- },
119
- (val) => val
120
- );
121
- state.status = 'succeed';
122
- } catch (error) {
123
- const errorMessage = error?.response?.data || error?.message;
124
- Toast.error(errorMessage);
125
- debug('Email login failed (use code)', { error });
126
- } finally {
127
- currentState.loadingVerifyCode = false;
128
- forceUpdate();
129
- }
130
- });
131
- const verifyMagicToken = useMemoizedFn(async () => {
132
- state.status = 'verifying';
133
- forceUpdate();
134
- try {
135
- const { data: loginResult } = await api.post(withQuery('/email/login', { locale }), {
136
- ...extraParams,
137
- action,
138
- magicToken: state.magicToken,
139
- });
140
- debug('Email login succeed (use magic link)', { loginResult });
141
- // 登录成功后记录最后使用的登录方式(用于显示 Last Used 标签)
142
- localStorage.setItem(LAST_USED_LOGIN_METHOD, LOGIN_PROVIDER.EMAIL);
143
- await onSuccess(
144
- {
145
- ...loginResult,
146
- encrypted: false,
147
- },
148
- (val) => val
149
- );
150
- state.status = 'succeed';
151
- } catch (error) {
152
- const errorMessage = error?.response?.data || error?.message;
153
- state.status = 'error';
154
- state.error = errorMessage;
155
- debug('Email login failed (use magic link)', { error });
156
- }
157
- forceUpdate();
158
- });
159
-
160
- const shouldPoll =
161
- currentState.verifyMode === 'magicLink' &&
162
- state.status === 'sending' &&
163
- // 如果正在校验验证码,则不轮询状态
164
- !currentState.loadingVerifyCode &&
165
- currentState.codeId;
166
-
167
- const checkStatus = useMemoizedFn(async () => {
168
- if (!shouldPoll) {
169
- return;
170
- }
171
- try {
172
- const { data } = await api.get('/email/status', {
173
- params: {
174
- codeId: currentState.codeId,
175
- },
176
- });
177
- if (data.verified) {
178
- state.status = 'succeed';
179
- reset();
180
- // 登录成功后记录最后使用的登录方式(用于显示 Last Used 标签)
181
- localStorage.setItem(LAST_USED_LOGIN_METHOD, LOGIN_PROVIDER.EMAIL);
182
- await onSuccess({ encrypted: false }, (val) => val);
183
- }
184
- } catch (error) {
185
- // 只做记录
186
- console.error('check status error', error);
187
- }
188
- forceUpdate();
189
- });
190
-
191
- useInterval(checkStatus, shouldPoll ? CHECK_STATUS_INTERVAL : undefined);
192
-
193
- useEffect(() => {
194
- reset();
195
- // FIXME: @zhanghan 暂不支持子站进行统一登录时使用 magicLink 登录
196
- if (connectState.sourceAppPid || !['login'].includes(action)) {
197
- currentState.defaultVerifyMode = 'code';
198
- }
199
- if (state.magicToken) {
200
- verifyMagicToken();
201
- }
202
- // 由于存在 email 插件被重置的情况,所以需要监听 state 是否被重新赋值来执行 init 操作
203
- // eslint-disable-next-line react-hooks/exhaustive-deps
204
- }, [state]);
205
-
206
- let content = null;
207
- if (state.status === 'creating') {
208
- content = (
209
- <>
210
- <Typography
211
- variant="body2"
212
- sx={{
213
- textAlign: 'center',
214
- color: 'text.secondary',
215
- }}>
216
- {t('emailPlaceholder')}
217
- </Typography>
218
- <TextField
219
- name="email"
220
- fullWidth
221
- type="email"
222
- variant="outlined"
223
- label={t('email')}
224
- placeholder={t('emailPlaceholder')}
225
- size="small"
226
- value={currentState.email}
227
- onChange={(e) => {
228
- currentState.email = e.target.value;
229
- }}
230
- onKeyDown={(e) => {
231
- if (e.key === 'Enter') {
232
- sendCode(currentState.email);
233
- }
234
- }}
235
- sx={{
236
- maxWidth: 320,
237
- '& .MuiOutlinedInput-root': {
238
- borderRadius: 1,
239
- },
240
- }}
241
- error={!!currentState.error}
242
- helperText={currentState.error}
243
- slotProps={{
244
- htmlInput: {
245
- sx: {
246
- boxSizing: 'content-box !important',
247
- },
248
- },
249
- }}
250
- />
251
- <Box sx={{ display: 'flex', gap: 1 }}>
252
- <BackButton
253
- onClick={() => {
254
- state.reset();
255
- setSelectedPlugin();
256
- }}
257
- />
258
- <ActionButton
259
- sx={{
260
- color: 'primary.main',
261
- borderColor: 'primary.light',
262
- }}
263
- disabled={currentState.loadingSendCode}
264
- onClick={() => {
265
- sendCode(currentState.email);
266
- }}>
267
- {currentState.loadingSendCode ? <CircularProgress color="inherit" size={14} sx={{ mr: 1 }} /> : null}
268
- {t('sendCode')}
269
- </ActionButton>
270
- </Box>
271
- </>
272
- );
273
- } else if (state.status === 'sending') {
274
- content = (
275
- <>
276
- <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
277
- <Typography variant="body2" sx={{ textAlign: 'center', color: 'text.secondary' }}>
278
- {currentState.verifyMode === 'magicLink' ? (
279
- <>
280
- {t('verifyEmail')}
281
- <br />
282
- {t('orUseCodePlaceholder')}
283
- </>
284
- ) : (
285
- t('codePlaceholder')
286
- )}
287
- </Typography>
288
- <>
289
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
290
- {/* 暂时保留代码,防止后期需要再使用 */}
291
- {/* <Link
292
- underline="none"
293
- variant="caption"
294
- component="button"
295
- sx={{ mb: 2 }}
296
- onClick={() => {
297
- currentState.verifyMode = 'code';
298
- forceUpdate();
299
- }}>
300
- {t('useCode')}
301
- </Link>
302
- <BackButton
303
- onClick={() => {
304
- state.status = 'creating';
305
- forceUpdate();
306
- }}
307
- /> */}
308
- </>
309
- </Box>
310
- <>
311
- <VerificationCode
312
- code={currentState.code}
313
- onChange={(code) => {
314
- currentState.code = code;
315
- }}
316
- onComplete={() => {
317
- verifyCode();
318
- }}
319
- />
320
- <Box sx={{ display: 'flex', gap: 1 }}>
321
- <BackButton
322
- onClick={() => {
323
- currentState.code = '';
324
- state.status = 'creating';
325
- forceUpdate();
326
- }}
327
- />
328
- <ActionButton
329
- sx={{
330
- color: 'primary.main',
331
- borderColor: 'primary.light',
332
- }}
333
- disabled={currentState.loadingVerifyCode}
334
- onClick={() => {
335
- verifyCode();
336
- }}>
337
- {currentState.loadingVerifyCode ? <CircularProgress color="inherit" size={14} sx={{ mr: 1 }} /> : null}
338
- {t('confirm')}
339
- </ActionButton>
340
- </Box>
341
- </>
342
- </>
343
- );
344
- } else {
345
- return fallback;
346
- }
347
-
348
- return (
349
- <Box
350
- sx={{
351
- display: 'flex',
352
- flexDirection: 'column',
353
- alignItems: 'center',
354
- justifyContent: 'center',
355
- gap: 2,
356
- pb: 1,
357
- }}>
358
- <LoadingMask size={52} borderRadius={12}>
359
- <ProviderIcon provider="email" />
360
- </LoadingMask>
361
- {content}
362
- </Box>
363
- );
364
- }
365
-
366
- EmailPlaceholder.propTypes = {
367
- fallback: PropTypes.any,
368
- state: PropTypes.object.isRequired,
369
- forceUpdate: PropTypes.func,
370
- onSuccess: PropTypes.func,
371
- t: PropTypes.func,
372
- };
@@ -1,2 +0,0 @@
1
- /* eslint-disable import/prefer-default-export */
2
- export { default as useEmailPlugin } from './email';