@arcblock/did-connect-react 3.1.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 (183) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +134 -0
  3. package/lib/Address/index.js +4 -0
  4. package/lib/Avatar/index.js +4 -0
  5. package/lib/Button/index.js +17 -0
  6. package/lib/Connect/assets/locale.js +143 -0
  7. package/lib/Connect/assets/login-bg.png +0 -0
  8. package/lib/Connect/assets/login-slogan.js +9 -0
  9. package/lib/Connect/components/action-button.js +26 -0
  10. package/lib/Connect/components/app-tips.js +132 -0
  11. package/lib/Connect/components/auto-height.js +31 -0
  12. package/lib/Connect/components/back-button.js +24 -0
  13. package/lib/Connect/components/connect-status.js +263 -0
  14. package/lib/Connect/components/did-connect-title.js +126 -0
  15. package/lib/Connect/components/download-tips.js +52 -0
  16. package/lib/Connect/components/loading.js +26 -0
  17. package/lib/Connect/components/login-item/connect-choose-list.js +249 -0
  18. package/lib/Connect/components/login-item/login-method-item.js +129 -0
  19. package/lib/Connect/components/login-item/mobile-login-item.js +114 -0
  20. package/lib/Connect/components/login-item/passkey-login-item.js +44 -0
  21. package/lib/Connect/components/login-item/web-login-item.js +97 -0
  22. package/lib/Connect/components/mask-overlay.js +34 -0
  23. package/lib/Connect/components/refresh-overlay.js +57 -0
  24. package/lib/Connect/components/switch-app.js +70 -0
  25. package/lib/Connect/contexts/state.js +142 -0
  26. package/lib/Connect/fullpage.js +5 -0
  27. package/lib/Connect/hooks/auth-url.js +23 -0
  28. package/lib/Connect/hooks/method-list.js +46 -0
  29. package/lib/Connect/hooks/page-show.js +17 -0
  30. package/lib/Connect/hooks/security.js +27 -0
  31. package/lib/Connect/hooks/token.js +305 -0
  32. package/lib/Connect/hooks/use-apps.js +19 -0
  33. package/lib/Connect/hooks/use-quick-connect.js +97 -0
  34. package/lib/Connect/index.js +498 -0
  35. package/lib/Connect/landing-page.js +5 -0
  36. package/lib/Connect/plugins/email/index.js +62 -0
  37. package/lib/Connect/plugins/email/list-item.js +28 -0
  38. package/lib/Connect/plugins/email/placeholder.js +283 -0
  39. package/lib/Connect/plugins/index.js +4 -0
  40. package/lib/Connect/use-connect.js +164 -0
  41. package/lib/Connect/with-blocklet.js +15 -0
  42. package/lib/Connect/with-bridge-call.js +108 -0
  43. package/lib/Federated/context.js +61 -0
  44. package/lib/Federated/index.js +7 -0
  45. package/lib/Logo/index.js +4 -0
  46. package/lib/OAuth/context.js +234 -0
  47. package/lib/OAuth/guest.svg.js +5 -0
  48. package/lib/OAuth/index.js +7 -0
  49. package/lib/OAuth/passport-switcher.js +114 -0
  50. package/lib/Passkey/actions.js +165 -0
  51. package/lib/Passkey/constants.js +4 -0
  52. package/lib/Passkey/context.js +266 -0
  53. package/lib/Passkey/dialog.js +277 -0
  54. package/lib/Passkey/icon.js +13 -0
  55. package/lib/Passkey/index.js +9 -0
  56. package/lib/Service/index.js +62 -0
  57. package/lib/Session/assets/did-spaces-guide-cover.svg.js +135 -0
  58. package/lib/Session/assets/did-spaces-guide-icon.svg.js +9 -0
  59. package/lib/Session/context.js +5 -0
  60. package/lib/Session/did-spaces-guide.js +136 -0
  61. package/lib/Session/hooks/use-federated.js +64 -0
  62. package/lib/Session/hooks/use-mobile.js +8 -0
  63. package/lib/Session/hooks/use-protected-routes.js +11 -0
  64. package/lib/Session/hooks/use-session-token.js +169 -0
  65. package/lib/Session/hooks/use-verify.js +45 -0
  66. package/lib/Session/index.js +896 -0
  67. package/lib/Session/libs/constants.js +15 -0
  68. package/lib/Session/libs/did-spaces.js +10 -0
  69. package/lib/Session/libs/federated.js +42 -0
  70. package/lib/Session/libs/index.js +15 -0
  71. package/lib/Session/libs/locales.js +161 -0
  72. package/lib/Session/libs/login-mobile.js +55 -0
  73. package/lib/Session/window-focus-aware.js +17 -0
  74. package/lib/SessionManager/index.js +4 -0
  75. package/lib/Storage/engine/cookie.js +21 -0
  76. package/lib/Storage/engine/local-storage.js +36 -0
  77. package/lib/Storage/index.js +23 -0
  78. package/lib/User/index.js +6 -0
  79. package/lib/User/use-did.js +59 -0
  80. package/lib/User/wrap-did.js +13 -0
  81. package/lib/WebWalletSWKeeper/index.js +5 -0
  82. package/lib/constant.js +22 -0
  83. package/lib/error.js +8 -0
  84. package/lib/hooks/use-locale.js +7 -0
  85. package/lib/index.js +33 -0
  86. package/lib/locales/en.js +17 -0
  87. package/lib/locales/index.js +10 -0
  88. package/lib/locales/zh.js +17 -0
  89. package/lib/package.json.js +7 -0
  90. package/lib/types.d.ts +355 -0
  91. package/lib/utils.js +214 -0
  92. package/package.json +84 -0
  93. package/src/Address/index.jsx +2 -0
  94. package/src/Avatar/index.jsx +2 -0
  95. package/src/Button/Button.stories.jsx +7 -0
  96. package/src/Button/index.jsx +21 -0
  97. package/src/Connect/Connect.stories.jsx +34 -0
  98. package/src/Connect/assets/locale.js +145 -0
  99. package/src/Connect/assets/login-bg.png +0 -0
  100. package/src/Connect/assets/login-slogan.js +7 -0
  101. package/src/Connect/components/action-button.jsx +22 -0
  102. package/src/Connect/components/app-tips.jsx +156 -0
  103. package/src/Connect/components/auto-height.jsx +38 -0
  104. package/src/Connect/components/back-button.jsx +23 -0
  105. package/src/Connect/components/connect-status.jsx +259 -0
  106. package/src/Connect/components/did-connect-title.jsx +106 -0
  107. package/src/Connect/components/download-tips.jsx +55 -0
  108. package/src/Connect/components/loading.jsx +25 -0
  109. package/src/Connect/components/login-item/connect-choose-list.jsx +304 -0
  110. package/src/Connect/components/login-item/login-method-item.jsx +118 -0
  111. package/src/Connect/components/login-item/mobile-login-item.jsx +179 -0
  112. package/src/Connect/components/login-item/passkey-login-item.jsx +52 -0
  113. package/src/Connect/components/login-item/web-login-item.jsx +149 -0
  114. package/src/Connect/components/mask-overlay.jsx +32 -0
  115. package/src/Connect/components/refresh-overlay.jsx +52 -0
  116. package/src/Connect/components/switch-app.jsx +69 -0
  117. package/src/Connect/contexts/state.jsx +219 -0
  118. package/src/Connect/fullpage.jsx +3 -0
  119. package/src/Connect/hooks/auth-url.js +31 -0
  120. package/src/Connect/hooks/method-list.js +121 -0
  121. package/src/Connect/hooks/page-show.js +24 -0
  122. package/src/Connect/hooks/security.js +40 -0
  123. package/src/Connect/hooks/token.js +639 -0
  124. package/src/Connect/hooks/use-apps.js +69 -0
  125. package/src/Connect/hooks/use-quick-connect.js +130 -0
  126. package/src/Connect/index.jsx +600 -0
  127. package/src/Connect/landing-page.jsx +3 -0
  128. package/src/Connect/plugins/email/index.jsx +82 -0
  129. package/src/Connect/plugins/email/list-item.jsx +31 -0
  130. package/src/Connect/plugins/email/placeholder.jsx +365 -0
  131. package/src/Connect/plugins/index.js +2 -0
  132. package/src/Connect/use-connect.jsx +321 -0
  133. package/src/Connect/with-blocklet.jsx +26 -0
  134. package/src/Connect/with-bridge-call.jsx +138 -0
  135. package/src/Federated/context.jsx +93 -0
  136. package/src/Federated/index.jsx +1 -0
  137. package/src/Logo/index.jsx +2 -0
  138. package/src/OAuth/context.jsx +346 -0
  139. package/src/OAuth/guest.svg +20 -0
  140. package/src/OAuth/index.jsx +1 -0
  141. package/src/OAuth/passport-switcher.jsx +133 -0
  142. package/src/Passkey/actions.jsx +212 -0
  143. package/src/Passkey/constants.js +2 -0
  144. package/src/Passkey/context.jsx +381 -0
  145. package/src/Passkey/dialog.jsx +391 -0
  146. package/src/Passkey/icon.jsx +10 -0
  147. package/src/Passkey/index.jsx +2 -0
  148. package/src/Service/index.jsx +96 -0
  149. package/src/Session/assets/did-spaces-guide-cover.svg +128 -0
  150. package/src/Session/assets/did-spaces-guide-icon.svg +7 -0
  151. package/src/Session/context.jsx +7 -0
  152. package/src/Session/did-spaces-guide.jsx +173 -0
  153. package/src/Session/hooks/use-federated.js +88 -0
  154. package/src/Session/hooks/use-mobile.jsx +6 -0
  155. package/src/Session/hooks/use-protected-routes.js +16 -0
  156. package/src/Session/hooks/use-session-token.js +365 -0
  157. package/src/Session/hooks/use-verify.jsx +76 -0
  158. package/src/Session/index.jsx +1687 -0
  159. package/src/Session/libs/constants.js +14 -0
  160. package/src/Session/libs/did-spaces.js +38 -0
  161. package/src/Session/libs/federated.js +79 -0
  162. package/src/Session/libs/index.js +5 -0
  163. package/src/Session/libs/locales.js +160 -0
  164. package/src/Session/libs/login-mobile.js +80 -0
  165. package/src/Session/window-focus-aware.jsx +28 -0
  166. package/src/SessionManager/index.jsx +2 -0
  167. package/src/Storage/engine/cookie.js +23 -0
  168. package/src/Storage/engine/local-storage.js +55 -0
  169. package/src/Storage/index.js +25 -0
  170. package/src/User/index.js +4 -0
  171. package/src/User/use-did.js +80 -0
  172. package/src/User/wrap-did.jsx +18 -0
  173. package/src/WebWalletSWKeeper/index.jsx +3 -0
  174. package/src/constant.js +26 -0
  175. package/src/error.js +6 -0
  176. package/src/hooks/use-locale.jsx +6 -0
  177. package/src/index.js +43 -0
  178. package/src/locales/en.jsx +15 -0
  179. package/src/locales/index.jsx +13 -0
  180. package/src/locales/zh.jsx +15 -0
  181. package/src/types.d.ts +355 -0
  182. package/src/utils.js +395 -0
  183. package/vite.config.mjs +29 -0
@@ -0,0 +1,600 @@
1
+ import { use, useEffect, useRef, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Box, Divider, Skeleton } from '@mui/material';
4
+ import {
5
+ useCreation,
6
+ useDebounceFn,
7
+ useMemoizedFn,
8
+ useMount,
9
+ usePrevious,
10
+ useSize,
11
+ useUpdate,
12
+ useUpdateEffect,
13
+ } from 'ahooks';
14
+ import noop from 'lodash/noop';
15
+ import isUndefined from 'lodash/isUndefined';
16
+
17
+ import CloseButton from '@arcblock/ux/lib/CloseButton';
18
+ import {
19
+ LOGIN_PROVIDER,
20
+ LOGIN_PROVIDER_NAME,
21
+ OAUTH_PROVIDER,
22
+ DID_CONNECT_MEDIUM_WIDTH,
23
+ DID_CONNECT_SMALL_WIDTH,
24
+ } from '@arcblock/ux/lib/Util/constant';
25
+ import QRCode from '@arcblock/ux/lib/QRCode';
26
+ import { DIDConnectFooter, withContainer, withUxTheme } from '@arcblock/ux/lib/DIDConnect';
27
+ import { useTheme } from '@arcblock/ux/lib/Theme';
28
+ import ProviderIcon from '@arcblock/ux/lib/DIDConnect/provider-icon';
29
+
30
+ import '@fontsource/lexend/400.css';
31
+ import '@fontsource/lexend/600.css';
32
+
33
+ import withBlocklet from './with-blocklet';
34
+ import withBridgeCall from './with-bridge-call';
35
+ import useSecurity from './hooks/security';
36
+
37
+ import { SessionContext } from '../Session/context';
38
+ import { StateProvider, useStateContext } from './contexts/state';
39
+ import ConnectChooseList from './components/login-item/connect-choose-list';
40
+ import AutoHeight from './components/auto-height';
41
+ import useToken from './hooks/token';
42
+ import { useOAuth } from '../OAuth';
43
+ import ConnectStatus from './components/connect-status';
44
+ import { usePasskey } from '../Passkey/context';
45
+ import { API_DID_PREFIX, DEFAULT_TIMEOUT, BUSY_STATUS, CHECK_STATUS_INTERVAL } from '../constant';
46
+ import { getWebWalletUrl } from '../utils';
47
+ import DIDConnectTitle from './components/did-connect-title';
48
+ import DownloadTips from './components/download-tips';
49
+ import useMethodList from './hooks/method-list';
50
+ import useAuthUrl from './hooks/auth-url';
51
+ import { getWalletDid } from '../User/use-did';
52
+
53
+ function Connect({
54
+ hideCloseButton = false,
55
+ mode = 'dialog',
56
+ action,
57
+ baseUrl = '',
58
+ checkFn,
59
+ checkInterval = CHECK_STATUS_INTERVAL,
60
+ checkTimeout = DEFAULT_TIMEOUT * 1000,
61
+ prefix = API_DID_PREFIX,
62
+ tokenKey = '_t_',
63
+ locale = 'en',
64
+ encKey = '_ek_',
65
+ autoConnect = true,
66
+ forceConnected = true,
67
+ saveConnect = true,
68
+ useSocket = true,
69
+ allowWallet = true,
70
+ provider = '',
71
+ messages = {},
72
+ passkeyBehavior = 'none',
73
+ webWalletUrl = getWebWalletUrl(),
74
+ enabledConnectTypes = ['web', 'mobile', ...Object.keys(OAUTH_PROVIDER)],
75
+ extraContent = null,
76
+ disableSwitchApp = false,
77
+ magicToken = undefined,
78
+ customItems = [],
79
+ onClose = noop,
80
+ onError = noop,
81
+ onSuccess = noop,
82
+ onRecreateSession = noop,
83
+ setColor = noop,
84
+ }) {
85
+ const theme = useTheme();
86
+ const forceUpdate = useUpdate();
87
+ const sessionCtx = use(SessionContext);
88
+ const currentUserDid = getWalletDid(sessionCtx?.session?.user);
89
+
90
+ const {
91
+ t,
92
+ staticState,
93
+ connectState,
94
+ extraParams,
95
+ currentAppInfo,
96
+ currentAppColor,
97
+ // 插件相关
98
+ selectedPlugin,
99
+ blocklet,
100
+ masterBlocklet,
101
+ } = useStateContext();
102
+ const { state, generate, cancelWhenScanned } = useToken({
103
+ action,
104
+ baseUrl,
105
+ checkFn,
106
+ checkInterval,
107
+ checkTimeout,
108
+ extraParams,
109
+ prefix,
110
+ onError,
111
+ onSuccess,
112
+ locale,
113
+ tokenKey,
114
+ encKey,
115
+ autoConnect,
116
+ forceConnected: forceConnected === true ? currentUserDid || true : forceConnected,
117
+ saveConnect,
118
+ useSocket,
119
+ allowWallet,
120
+ provider,
121
+ });
122
+ const refreshingToken = useRef(false);
123
+ const rootRef = useRef(null);
124
+ const size = useSize(rootRef);
125
+ const isSmallView = useCreation(() => {
126
+ if (size) {
127
+ return size.width < DID_CONNECT_MEDIUM_WIDTH - 50;
128
+ }
129
+ return true;
130
+ }, [size, size?.width]);
131
+
132
+ const [isInitSmallView, setIsInitSmallView] = useState(false);
133
+
134
+ useMount(() => {
135
+ setIsInitSmallView(size?.width < DID_CONNECT_MEDIUM_WIDTH - 50);
136
+ });
137
+
138
+ const { oauthState, setBaseUrl } = useOAuth();
139
+ const { passkeyState, setTargetAppPid } = usePasskey();
140
+
141
+ useMount(() => {
142
+ setBaseUrl(baseUrl);
143
+ setTargetAppPid(blocklet?.appPid);
144
+ // 每次打开 did-connect,都重置状态
145
+ state.reset();
146
+ oauthState.reset();
147
+ passkeyState.reset();
148
+ });
149
+
150
+ useEffect(() => {
151
+ setColor(currentAppColor);
152
+ // eslint-disable-next-line react-hooks/exhaustive-deps
153
+ }, [currentAppColor]);
154
+
155
+ const statusMessages = useCreation(() => {
156
+ return {
157
+ confirm: messages.confirm,
158
+ success: messages.success,
159
+ error: selectedPlugin?.state?.error || state.error || passkeyState.error || oauthState.error || '',
160
+ };
161
+ }, [
162
+ messages.confirm,
163
+ messages.success,
164
+ state.error,
165
+ oauthState.error,
166
+ passkeyState.error,
167
+ selectedPlugin?.state?.error,
168
+ ]);
169
+
170
+ const showStatus = useCreation(() => {
171
+ return (
172
+ BUSY_STATUS.includes(passkeyState.status) ||
173
+ BUSY_STATUS.includes(oauthState.status) ||
174
+ BUSY_STATUS.includes(state.status) ||
175
+ BUSY_STATUS.includes(selectedPlugin?.state?.computedStatus)
176
+ );
177
+ }, [state.status, oauthState.status, passkeyState.status, selectedPlugin?.state?.computedStatus]);
178
+
179
+ const handleReset = useMemoizedFn(async () => {
180
+ onRecreateSession();
181
+ oauthState.reset();
182
+ passkeyState.reset();
183
+ await generate(false);
184
+ });
185
+ const handleRetry = useMemoizedFn(() => {
186
+ connectState?.retryConnect();
187
+ });
188
+ const handleCancel = useMemoizedFn(() => {
189
+ onRecreateSession();
190
+ oauthState.reset();
191
+ passkeyState.reset();
192
+ selectedPlugin?.state?.reset();
193
+ staticState.current.cancelCount++;
194
+ cancelWhenScanned();
195
+ });
196
+
197
+ const { run: debounceHandleTimeout } = useDebounceFn(
198
+ () => {
199
+ if (refreshingToken.current) return;
200
+ if (state.status === 'timeout') {
201
+ refreshingToken.current = true;
202
+ state.reset();
203
+ handleReset();
204
+ refreshingToken.current = false;
205
+ }
206
+ },
207
+ { leading: true, trailing: false }
208
+ );
209
+ // eslint-disable-next-line react-hooks/exhaustive-deps
210
+ useEffect(debounceHandleTimeout, [state.status]);
211
+
212
+ const chooseMethod = useCreation(() => {
213
+ return LOGIN_PROVIDER_NAME[connectState.chooseMethod] || 'DID Wallet';
214
+ }, [connectState.chooseMethod]);
215
+
216
+ const {
217
+ showMobileLogin,
218
+ hideChooseList,
219
+ oauthProviderList,
220
+ showOAuthLogin,
221
+ showPasskeyLogin,
222
+ showWebLogin,
223
+ showEmailLogin,
224
+ hideQRCode,
225
+ } = useMethodList({
226
+ action: state.action,
227
+ sourceAppPid: connectState?.sourceAppPid,
228
+ enabledConnectTypes,
229
+ allowWallet,
230
+ passkeyBehavior,
231
+ webWalletUrl,
232
+ mode,
233
+ blocklet: connectState?.sourceAppPid ? masterBlocklet : blocklet,
234
+ isSmallView,
235
+ });
236
+
237
+ const prevSourceAppPid = usePrevious(connectState?.sourceAppPid);
238
+
239
+ // 切换了当前展示的应用后,需要重新生成二维码
240
+ useUpdateEffect(() => {
241
+ // HACK: connectState.sourceAppPid 是用户在页面中执行操作修改的值,最开始的时候就是 undefined,并在 did-connect 页面加载后,会由 app-info 触发一次更新(无论如何都会触发,这一次更新不应该触发 generate)
242
+ if (isUndefined(prevSourceAppPid)) {
243
+ return;
244
+ }
245
+ generate();
246
+ }, [connectState?.sourceAppPid]);
247
+
248
+ const getSpacingNumber = (value) => {
249
+ const spacingValue = theme.spacing(value);
250
+ return parseInt(spacingValue, 10);
251
+ };
252
+ const closeButton = useCreation(() => {
253
+ return hideCloseButton ? null : (
254
+ <CloseButton
255
+ onClose={onClose}
256
+ sx={{
257
+ position: 'absolute',
258
+ right: 14,
259
+ top: 14,
260
+ }}
261
+ />
262
+ );
263
+ }, [hideCloseButton, onClose]);
264
+
265
+ let content = null;
266
+ if (showStatus) {
267
+ content = (
268
+ <Box
269
+ sx={{
270
+ flex: 1,
271
+ display: 'flex',
272
+ alignItems: 'center',
273
+ justifyContent: 'center',
274
+ }}>
275
+ <Box>
276
+ <ConnectStatus
277
+ status={selectedPlugin?.state?.computedStatus || oauthState.status || passkeyState.status || state.status}
278
+ nextWorkflow={state.nextWorkflow}
279
+ mfaCode={state.mfaCode}
280
+ onCancel={handleCancel}
281
+ onRetry={handleRetry}
282
+ onClose={onClose}
283
+ messages={statusMessages}
284
+ locale={locale}
285
+ className="did-connect__auth-status auth-status"
286
+ loadingIcon={
287
+ connectState.chooseMethod ? (
288
+ <ProviderIcon provider={connectState.chooseMethod} sx={{ color: 'text.primary' }} />
289
+ ) : null
290
+ }
291
+ chooseMethod={chooseMethod}
292
+ hideRetry={connectState.chooseMethod === 'email'}
293
+ hideBack={!!magicToken}
294
+ />
295
+ </Box>
296
+ </Box>
297
+ );
298
+ }
299
+
300
+ const urlWithParams = useAuthUrl({ disableSwitchApp, tokenState: state });
301
+
302
+ // 防止二维码抖动,使用 useCreation 进行缓存
303
+ const qrcode = useCreation(() => {
304
+ const backgroundColor = theme.mode === 'dark' ? theme.palette.grey[500] : 'white';
305
+ return (
306
+ <Box
307
+ sx={{
308
+ p: hideChooseList ? 3 : 2,
309
+ width: (hideChooseList ? 240 : 196) + getSpacingNumber(1) * 2,
310
+ height: (hideChooseList ? 240 : 196) + getSpacingNumber(1) * 2,
311
+ }}>
312
+ {state.url ? (
313
+ <QRCode
314
+ data={urlWithParams}
315
+ size={hideChooseList ? 240 - getSpacingNumber(3) * 2 : 196 - getSpacingNumber(2) * 2}
316
+ sx={{
317
+ width:
318
+ (hideChooseList ? 240 - getSpacingNumber(3) * 2 : 196 - getSpacingNumber(2) * 2) +
319
+ getSpacingNumber(1) * 2,
320
+ height:
321
+ (hideChooseList ? 240 - getSpacingNumber(3) * 2 : 196 - getSpacingNumber(2) * 2) +
322
+ getSpacingNumber(1) * 2,
323
+ flex: 1,
324
+ backgroundColor,
325
+ p: 1,
326
+ fontSize: 0,
327
+ textAlign: 'center',
328
+ boxSizing: 'border-box',
329
+ borderRadius: 1,
330
+ border: '1px solid',
331
+ borderColor: 'divider',
332
+ }}
333
+ config={{
334
+ backgroundOptions: {
335
+ color: backgroundColor,
336
+ },
337
+ }}
338
+ />
339
+ ) : (
340
+ <Skeleton
341
+ animation="wave"
342
+ variant="rectangular"
343
+ sx={{
344
+ position: 'absolute',
345
+ left: getSpacingNumber(2) + 1,
346
+ right: getSpacingNumber(2) + 1,
347
+ top: getSpacingNumber(2) + 1,
348
+ bottom: getSpacingNumber(2) + 1,
349
+ borderRadius: 1,
350
+ zIndex: 1,
351
+ width: 'auto',
352
+ height: 'auto',
353
+ }}
354
+ />
355
+ )}
356
+ </Box>
357
+ );
358
+ }, [urlWithParams, hideChooseList]);
359
+
360
+ const didConnectFlexDirection = useCreation(() => {
361
+ if (hideChooseList) {
362
+ return 'column-reverse';
363
+ }
364
+ if (!hideQRCode && isInitSmallView) {
365
+ return 'column';
366
+ }
367
+ return 'row';
368
+ }, [hideChooseList, isInitSmallView, hideQRCode]);
369
+
370
+ const fallbackContent = (
371
+ <Box
372
+ className="did-connect__body"
373
+ sx={{
374
+ display: 'flex',
375
+ flexDirection: didConnectFlexDirection,
376
+ justifyContent: 'center',
377
+ alignItems: 'stretch',
378
+ flex: 1,
379
+ gap: !hideQRCode && isSmallView ? 0 : 1.5,
380
+ overflow: 'visible',
381
+ px: hideChooseList ? 2 : 0,
382
+ }}>
383
+ {!showStatus && !hideQRCode ? (
384
+ <>
385
+ <Box
386
+ sx={{
387
+ display: 'flex',
388
+ alignItems: 'center',
389
+ overflowX: 'auto',
390
+ overflowY: 'visible',
391
+ maxWidth: '100%',
392
+ margin: 'auto',
393
+ }}>
394
+ <Box
395
+ sx={{
396
+ fontSize: 0,
397
+ position: 'relative',
398
+ mb: hideChooseList ? 4 : 2.5,
399
+ }}>
400
+ {qrcode}
401
+ <Box
402
+ sx={{
403
+ position: 'absolute',
404
+ color: 'text.secondary',
405
+ fontSize: 12,
406
+ bottom: hideChooseList ? 8 : 4,
407
+ transform: 'translateY(100%)',
408
+ left: 0,
409
+ right: 0,
410
+ textAlign: 'center',
411
+ }}>
412
+ {t('scanWithWallet1')} <DownloadTips /> {t('scanWithWallet2')}
413
+ </Box>
414
+ </Box>
415
+ </Box>
416
+ {hideChooseList ? null : (
417
+ <Box>
418
+ <Divider
419
+ orientation="vertical"
420
+ sx={{
421
+ fontSize: 12,
422
+ color: 'text.hint',
423
+ '&::before, &::after': {
424
+ borderColor: 'divider',
425
+ },
426
+ }}>
427
+ or
428
+ </Divider>
429
+ </Box>
430
+ )}
431
+ </>
432
+ ) : null}
433
+ <Box
434
+ sx={{
435
+ display: 'flex',
436
+ flex: 1,
437
+ }}>
438
+ {/* eslint-disable-next-line no-nested-ternary */}
439
+ {content}
440
+ <ConnectChooseList
441
+ slotProps={{
442
+ root: {
443
+ sx: [showStatus ? { display: 'none' } : {}],
444
+ },
445
+ }}
446
+ allowWallet={allowWallet}
447
+ size={hideQRCode && mode !== 'dialog' ? 'normal' : 'small'}
448
+ tokenState={state}
449
+ messages={messages}
450
+ tokenKey={tokenKey}
451
+ onSuccess={onSuccess}
452
+ passkeyBehavior={passkeyBehavior}
453
+ webWalletUrl={webWalletUrl}
454
+ extraContent={extraContent}
455
+ enabledConnectTypes={enabledConnectTypes}
456
+ onRest={handleReset}
457
+ showMobileLogin={showMobileLogin}
458
+ showOAuthLogin={showOAuthLogin}
459
+ showPasskeyLogin={showPasskeyLogin}
460
+ showWebLogin={showWebLogin}
461
+ showEmailLogin={showEmailLogin}
462
+ oauthProviderList={oauthProviderList}
463
+ disableSwitchApp={disableSwitchApp}
464
+ forceUpdate={forceUpdate}
465
+ magicToken={magicToken}
466
+ baseUrl={baseUrl}
467
+ customItems={customItems}
468
+ />
469
+ </Box>
470
+ </Box>
471
+ );
472
+ let contentResult = fallbackContent;
473
+ if (selectedPlugin) {
474
+ contentResult = selectedPlugin.renderPlaceholder({
475
+ fallback: fallbackContent,
476
+ forceUpdate,
477
+ onSuccess,
478
+ onError,
479
+ });
480
+ } else {
481
+ contentResult = fallbackContent;
482
+ }
483
+
484
+ return (
485
+ <Box
486
+ ref={rootRef}
487
+ className="did-connect__root"
488
+ sx={{
489
+ backgroundColor: 'background.default',
490
+ display: 'flex',
491
+ flexDirection: 'column',
492
+ height: '100%',
493
+ position: 'relative',
494
+ maxWidth: '100%',
495
+ width:
496
+ // eslint-disable-next-line no-nested-ternary
497
+ mode === 'drawer'
498
+ ? '100%'
499
+ : hideQRCode || showStatus || hideChooseList
500
+ ? DID_CONNECT_SMALL_WIDTH
501
+ : DID_CONNECT_MEDIUM_WIDTH,
502
+ transition: 'width 0.2s ease-in-out',
503
+ margin: 'auto',
504
+ p: isSmallView ? 2 : 3,
505
+ pb: 0,
506
+ gap: 2,
507
+ }}>
508
+ <Box data-did-auth-url={state.url} sx={{ display: 'none' }} />
509
+ <DIDConnectTitle
510
+ title={messages.title}
511
+ description={messages.scan}
512
+ extraContent={extraContent}
513
+ disableSwitchApp={disableSwitchApp}
514
+ />
515
+ <AutoHeight initHeight={24 + 16 + 32}>{contentResult}</AutoHeight>
516
+ <DIDConnectFooter currentAppInfo={currentAppInfo} currentAppColor={currentAppColor} />
517
+ {closeButton}
518
+ </Box>
519
+ );
520
+ }
521
+
522
+ Connect.propTypes = {
523
+ mode: PropTypes.oneOf(['dialog', 'drawer', 'page']),
524
+
525
+ action: PropTypes.string.isRequired,
526
+ baseUrl: PropTypes.string,
527
+ checkFn: PropTypes.func.isRequired,
528
+ checkInterval: PropTypes.number,
529
+ checkTimeout: PropTypes.number,
530
+ // extraParams: PropTypes.object, // 需要使用 useStateContext 中导出的
531
+ prefix: PropTypes.string,
532
+ messages: PropTypes.object,
533
+ tokenKey: PropTypes.string,
534
+ locale: PropTypes.oneOf(['en', 'zh', '']),
535
+ encKey: PropTypes.string,
536
+ autoConnect: PropTypes.bool,
537
+ forceConnected: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
538
+ saveConnect: PropTypes.bool,
539
+ useSocket: PropTypes.bool,
540
+ extraContent: PropTypes.any,
541
+ passkeyBehavior: PropTypes.oneOf(['none', 'both', 'only-existing', 'only-new']),
542
+ enabledConnectTypes: PropTypes.arrayOf(PropTypes.oneOf(['web', 'mobile', ...Object.keys(OAUTH_PROVIDER)])),
543
+ webWalletUrl: PropTypes.string,
544
+
545
+ allowWallet: PropTypes.bool,
546
+ provider: PropTypes.oneOf([LOGIN_PROVIDER.WALLET, ...Object.keys(OAUTH_PROVIDER), '']),
547
+ hideCloseButton: PropTypes.bool,
548
+ disableSwitchApp: PropTypes.bool,
549
+ onClose: PropTypes.func,
550
+ onError: PropTypes.func,
551
+ onSuccess: PropTypes.func,
552
+ onRecreateSession: PropTypes.func,
553
+ setColor: PropTypes.func,
554
+ magicToken: PropTypes.string,
555
+ customItems: PropTypes.arrayOf(PropTypes.node),
556
+ };
557
+
558
+ function WrapConnect({ testOnlyBorderColor = undefined, ...props }) {
559
+ const { checkFn, extraParams = {}, blocklet, masterBlocklet, action, locale = 'en' } = props;
560
+ if (typeof checkFn !== 'function') {
561
+ throw new Error('Cannot initialize did connect component without a fetchFn');
562
+ }
563
+
564
+ return (
565
+ <StateProvider
566
+ blocklet={blocklet}
567
+ masterBlocklet={masterBlocklet}
568
+ action={action}
569
+ locale={locale}
570
+ extraParams={extraParams}
571
+ testOnlyBorderColor={testOnlyBorderColor}
572
+ sx={{
573
+ position: 'relative',
574
+ width: '100%',
575
+ height: '100%',
576
+ lineHeight: 1.2,
577
+ color: 'grey.700',
578
+ '&, & *, & *:before, & *:after': {
579
+ fontFamily: 'Lexend', // 保持跟 DID Wallet 一致
580
+ boxSizing: 'border-box',
581
+ },
582
+ }}>
583
+ <Connect {...props} />
584
+ </StateProvider>
585
+ );
586
+ }
587
+
588
+ WrapConnect.propTypes = {
589
+ checkFn: PropTypes.func.isRequired,
590
+ extraParams: PropTypes.object,
591
+ blocklet: PropTypes.object.isRequired,
592
+ masterBlocklet: PropTypes.object,
593
+ action: PropTypes.string.isRequired,
594
+ locale: PropTypes.string,
595
+ testOnlyBorderColor: PropTypes.string,
596
+ };
597
+
598
+ export default withUxTheme(withBridgeCall(withBlocklet(withContainer(WrapConnect))));
599
+
600
+ export { useSecurity };
@@ -0,0 +1,3 @@
1
+ import LandingPage from '@arcblock/ux/lib/DIDConnect/landing-page';
2
+
3
+ export default LandingPage;
@@ -0,0 +1,82 @@
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
+
9
+ export default function useEmailPlugin({ baseUrl }) {
10
+ const state = useReactive({
11
+ baseUrl,
12
+ status: 'idle',
13
+ get computedStatus() {
14
+ if (this.status === 'idle') {
15
+ return 'creating';
16
+ }
17
+ if (this.status === 'creating') {
18
+ return 'scanned';
19
+ }
20
+ if (this.status === 'sending') {
21
+ return 'scanned';
22
+ }
23
+ if (this.status === 'verifying') {
24
+ return 'scanned';
25
+ }
26
+ return this.status;
27
+ },
28
+ error: '',
29
+ magicToken: '',
30
+ reset() {
31
+ this.status = 'idle';
32
+ this.error = '';
33
+ this.magicToken = '';
34
+ },
35
+ });
36
+ const name = LOGIN_PROVIDER.EMAIL;
37
+ const title = 'Email';
38
+ const icon = mailOutlineRoundedIcon;
39
+ const translations = {
40
+ zh: {
41
+ email: '邮箱地址',
42
+ emailPlaceholder: '请输入邮箱地址',
43
+ sendCode: '发送验证邮件',
44
+ confirm: '确认',
45
+ verifyEmail: '登录链接已发送至邮箱,请登录邮箱查看',
46
+ useCode: '使用验证码',
47
+ codePlaceholder: '请输入验证码',
48
+ orUseCodePlaceholder: '或使用验证码',
49
+ emailInvalid: '邮箱格式不正确',
50
+ emailRequired: '邮箱地址不能为空',
51
+ },
52
+ en: {
53
+ email: 'Email',
54
+ emailPlaceholder: 'Enter your email address',
55
+ sendCode: 'Send Verification Email',
56
+ confirm: 'Confirm',
57
+ verifyEmail: 'Login link sent! Please check your email.',
58
+ useCode: 'Use Verification Code',
59
+ codePlaceholder: 'Enter your verification code.',
60
+ orUseCodePlaceholder: 'Or use verification code.',
61
+ emailInvalid: 'Email format is incorrect',
62
+ emailRequired: 'Email is required',
63
+ },
64
+ };
65
+ const t = useMemoizedFn((key, data = {}, locale = 'en') => {
66
+ return translate(translations, key, locale, 'en', data);
67
+ });
68
+
69
+ return {
70
+ name,
71
+ title,
72
+ icon,
73
+ renderListItem(props) {
74
+ return <EmailListItem {...props} state={state} name={name} title={title} icon={icon} t={t} />;
75
+ },
76
+ renderPlaceholder(props) {
77
+ return <EmailPlaceholder {...props} state={state} name={name} title={title} icon={icon} t={t} />;
78
+ },
79
+ order: 100,
80
+ state,
81
+ };
82
+ }