@arcblock/did-connect-react 3.3.10 → 3.4.1

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 (39) hide show
  1. package/lib/Connect/assets/locale.js +4 -2
  2. package/lib/Connect/components/login-item/connect-choose-list.js +120 -114
  3. package/lib/Connect/components/login-item/connect-provider-list.js +187 -180
  4. package/lib/Connect/components/login-item/login-method-item.js +63 -47
  5. package/lib/Connect/components/login-item/passkey-login-item.js +17 -15
  6. package/lib/Connect/components/login-item/wallet-login-options.js +42 -40
  7. package/lib/Connect/connect.js +503 -0
  8. package/lib/Connect/contexts/state.js +74 -65
  9. package/lib/Connect/fallback-connect.js +53 -0
  10. package/lib/Connect/hooks/provider-list.js +45 -29
  11. package/lib/Connect/index.js +13 -502
  12. package/lib/Connect/plugins/email/list-item.js +19 -18
  13. package/lib/Connect/use-connect.js +10 -10
  14. package/lib/OAuth/context.js +3 -3
  15. package/lib/Passkey/actions.js +80 -75
  16. package/lib/Passkey/context.js +3 -3
  17. package/lib/Passkey/dialog.js +7 -7
  18. package/lib/Session/assets/did-spaces-guide-cover.svg.js +74 -98
  19. package/lib/Session/index.js +189 -189
  20. package/lib/package.json.js +1 -1
  21. package/lib/utils.js +2 -2
  22. package/package.json +8 -8
  23. package/src/Connect/assets/locale.js +2 -0
  24. package/src/Connect/components/login-item/connect-choose-list.jsx +12 -5
  25. package/src/Connect/components/login-item/connect-provider-list.jsx +11 -4
  26. package/src/Connect/components/login-item/login-method-item.jsx +24 -7
  27. package/src/Connect/components/login-item/passkey-login-item.jsx +3 -1
  28. package/src/Connect/components/login-item/wallet-login-options.jsx +3 -2
  29. package/src/Connect/connect.jsx +618 -0
  30. package/src/Connect/contexts/state.jsx +10 -0
  31. package/src/Connect/fallback-connect.jsx +47 -0
  32. package/src/Connect/hooks/provider-list.js +48 -17
  33. package/src/Connect/index.jsx +8 -605
  34. package/src/Connect/plugins/email/list-item.jsx +3 -2
  35. package/src/Connect/use-connect.jsx +1 -1
  36. package/src/Passkey/actions.jsx +5 -0
  37. package/src/Session/assets/did-spaces-guide-cover.svg +1 -128
  38. package/src/Session/index.jsx +3 -4
  39. package/src/utils.js +2 -2
@@ -0,0 +1,618 @@
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 } 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 { SessionContext } from '../Session/context';
34
+ import { StateProvider, useStateContext } from './contexts/state';
35
+ import ConnectProviderList from './components/login-item/connect-provider-list';
36
+ import AutoHeight from './components/auto-height';
37
+ import useToken from './hooks/token';
38
+ import { useOAuth } from '../OAuth';
39
+ import ConnectStatus from './components/connect-status';
40
+ import { usePasskey } from '../Passkey/context';
41
+ import { API_DID_PREFIX, DEFAULT_TIMEOUT, BUSY_STATUS, CHECK_STATUS_INTERVAL } from '../constant';
42
+ import { getWebWalletUrl } from '../utils';
43
+ import DIDConnectTitle from './components/did-connect-title';
44
+ import DownloadTips from './components/download-tips';
45
+ import useProviderList from './hooks/provider-list';
46
+ import useAuthUrl from './hooks/auth-url';
47
+ import { getWalletDid } from '../User/use-did';
48
+ import FallbackConnect from './fallback-connect';
49
+
50
+ function Connect({
51
+ hideCloseButton = false,
52
+ mode = 'dialog',
53
+ action,
54
+ baseUrl = '',
55
+ checkFn,
56
+ checkInterval = CHECK_STATUS_INTERVAL,
57
+ checkTimeout = DEFAULT_TIMEOUT * 1000,
58
+ prefix = API_DID_PREFIX,
59
+ tokenKey = '_t_',
60
+ locale = 'en',
61
+ encKey = '_ek_',
62
+ autoConnect = true,
63
+ forceConnected = true,
64
+ saveConnect = true,
65
+ useSocket = true,
66
+ allowWallet = true,
67
+ provider = '',
68
+ messages = {},
69
+ passkeyBehavior = 'none',
70
+ webWalletUrl = getWebWalletUrl(),
71
+ enabledConnectTypes = ['web', 'mobile', ...Object.keys(OAUTH_PROVIDER)],
72
+ extraContent = null,
73
+ disableSwitchApp = false,
74
+ magicToken = undefined,
75
+ customItems = [],
76
+ onClose = noop,
77
+ onError = noop,
78
+ onSuccess = noop,
79
+ onRecreateSession = noop,
80
+ setColor = noop,
81
+ }) {
82
+ const theme = useTheme();
83
+ const forceUpdate = useUpdate();
84
+ const sessionCtx = use(SessionContext);
85
+ const currentUserDid = getWalletDid(sessionCtx?.session?.user);
86
+
87
+ const {
88
+ t,
89
+ staticState,
90
+ connectState,
91
+ extraParams,
92
+ currentAppInfo,
93
+ currentAppColor,
94
+ // 插件相关
95
+ selectedPlugin,
96
+ blocklet,
97
+ masterBlocklet,
98
+ showWalletOptions,
99
+ setShowWalletOptions,
100
+ lastLoginMethod,
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 { providerList, hideQRCode, hideChooseList, loadingProviderList } = useProviderList({
217
+ action: state.action,
218
+ sourceAppPid: connectState?.sourceAppPid,
219
+ allowWallet,
220
+ passkeyBehavior,
221
+ webWalletUrl,
222
+ mode,
223
+ blocklet: connectState?.sourceAppPid ? masterBlocklet : blocklet,
224
+ isSmallView,
225
+ lastLoginMethod,
226
+ });
227
+
228
+ const prevSourceAppPid = usePrevious(connectState?.sourceAppPid);
229
+
230
+ // 切换了当前展示的应用后,需要重新生成二维码
231
+ useUpdateEffect(() => {
232
+ // HACK: connectState.sourceAppPid 是用户在页面中执行操作修改的值,最开始的时候就是 undefined,并在 did-connect 页面加载后,会由 app-info 触发一次更新(无论如何都会触发,这一次更新不应该触发 generate)
233
+ if (isUndefined(prevSourceAppPid)) {
234
+ return;
235
+ }
236
+ generate();
237
+ }, [connectState?.sourceAppPid]);
238
+
239
+ const getSpacingNumber = (value) => {
240
+ const spacingValue = theme.spacing(value);
241
+ return parseInt(spacingValue, 10);
242
+ };
243
+ const closeButton = useCreation(() => {
244
+ return hideCloseButton ? null : (
245
+ <CloseButton
246
+ onClose={onClose}
247
+ sx={{
248
+ position: 'absolute',
249
+ right: 14,
250
+ top: 14,
251
+ }}
252
+ />
253
+ );
254
+ }, [hideCloseButton, onClose]);
255
+
256
+ let content = null;
257
+ if (showStatus) {
258
+ content = (
259
+ <Box
260
+ sx={{
261
+ flex: 1,
262
+ display: 'flex',
263
+ alignItems: 'center',
264
+ justifyContent: 'center',
265
+ }}>
266
+ <Box>
267
+ <ConnectStatus
268
+ status={selectedPlugin?.state?.computedStatus || oauthState.status || passkeyState.status || state.status}
269
+ nextWorkflow={state.nextWorkflow}
270
+ mfaCode={state.mfaCode}
271
+ onCancel={handleCancel}
272
+ onRetry={handleRetry}
273
+ onClose={onClose}
274
+ messages={statusMessages}
275
+ locale={locale}
276
+ className="did-connect__auth-status auth-status"
277
+ loadingIcon={
278
+ connectState.chooseMethod ? (
279
+ <ProviderIcon provider={connectState.chooseMethod} sx={{ color: 'text.primary' }} />
280
+ ) : null
281
+ }
282
+ chooseMethod={chooseMethod}
283
+ hideRetry={connectState.chooseMethod === 'email'}
284
+ hideBack={!!magicToken}
285
+ />
286
+ </Box>
287
+ </Box>
288
+ );
289
+ }
290
+
291
+ const urlWithParams = useAuthUrl({ disableSwitchApp, tokenState: state });
292
+
293
+ // 防止二维码抖动,使用 useCreation 进行缓存
294
+ const qrcode = useCreation(() => {
295
+ const backgroundColor = theme.mode === 'dark' ? theme.palette.grey[600] : 'white';
296
+
297
+ let qrcodeSize = hideChooseList ? 240 - getSpacingNumber(3) * 2 : 196 - getSpacingNumber(2) * 2;
298
+ let padding = hideChooseList ? 3 : 2;
299
+ // 如果显示钱包登录,将 download tips 移动到顶部,调整间距,增加二维码的尺寸和内边距
300
+ if (showWalletOptions) {
301
+ padding = 1;
302
+ qrcodeSize += getSpacingNumber(padding) * 2;
303
+ }
304
+ return (
305
+ <Box
306
+ className="did-connect__qrcode"
307
+ sx={{
308
+ p: padding,
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={qrcodeSize}
316
+ sx={{
317
+ width: qrcodeSize + getSpacingNumber(1) * 2,
318
+ height: qrcodeSize + getSpacingNumber(1) * 2,
319
+ flex: 1,
320
+ backgroundColor,
321
+ p: 1,
322
+ fontSize: 0,
323
+ textAlign: 'center',
324
+ boxSizing: 'border-box',
325
+ borderRadius: 1,
326
+ border: '1px solid',
327
+ borderColor: 'divider',
328
+ }}
329
+ config={{
330
+ backgroundOptions: {
331
+ color: backgroundColor,
332
+ },
333
+ }}
334
+ />
335
+ ) : (
336
+ <Skeleton
337
+ animation="wave"
338
+ variant="rectangular"
339
+ sx={{
340
+ position: 'absolute',
341
+ left: getSpacingNumber(2) + 1,
342
+ right: getSpacingNumber(2) + 1,
343
+ top: getSpacingNumber(2) + 1,
344
+ bottom: getSpacingNumber(2) + 1,
345
+ borderRadius: 1,
346
+ zIndex: 1,
347
+ width: 'auto',
348
+ height: 'auto',
349
+ }}
350
+ />
351
+ )}
352
+ </Box>
353
+ );
354
+ }, [urlWithParams, hideChooseList, showWalletOptions]);
355
+
356
+ const didConnectFlexDirection = useCreation(() => {
357
+ if (hideChooseList) {
358
+ return 'column-reverse';
359
+ }
360
+ if (!hideQRCode && isInitSmallView) {
361
+ return 'column';
362
+ }
363
+ return 'row';
364
+ }, [hideChooseList, isInitSmallView, hideQRCode]);
365
+
366
+ const qrcodeContent = useCreation(() => {
367
+ if (!qrcode) return null;
368
+ return (
369
+ <Box
370
+ sx={{
371
+ display: 'flex',
372
+ alignItems: 'center',
373
+ overflowX: 'auto',
374
+ overflowY: 'visible',
375
+ maxWidth: '100%',
376
+ margin: 'auto',
377
+ }}>
378
+ <Box
379
+ sx={{
380
+ fontSize: 0,
381
+ position: 'relative',
382
+ ...(showWalletOptions
383
+ ? {
384
+ mt: hideChooseList ? 4 : 2.5,
385
+ mb: hideChooseList ? 0 : 0,
386
+ }
387
+ : {
388
+ mb: hideChooseList ? 4 : 2.5,
389
+ mt: 0,
390
+ }),
391
+ }}>
392
+ {qrcode}
393
+ <Box
394
+ sx={{
395
+ position: 'absolute',
396
+ color: 'text.secondary',
397
+ fontSize: 12,
398
+ zIndex: 1,
399
+ whiteSpace: 'nowrap',
400
+ ...(showWalletOptions
401
+ ? {
402
+ top: hideChooseList ? -8 : -4,
403
+ transform: 'translateY(-100%)',
404
+ }
405
+ : {
406
+ bottom: hideChooseList ? -8 : -4,
407
+ transform: 'translateY(100%)',
408
+ }),
409
+ left: 0,
410
+ right: 0,
411
+ textAlign: 'center',
412
+ }}>
413
+ {t('scanWithWallet1')} <DownloadTips /> {t('scanWithWallet2')}
414
+ </Box>
415
+ </Box>
416
+ </Box>
417
+ );
418
+ }, [qrcode, hideChooseList, showWalletOptions]);
419
+
420
+ const fallbackContent = (
421
+ <Box
422
+ className="did-connect__body"
423
+ sx={{
424
+ display: 'flex',
425
+ flexDirection: didConnectFlexDirection,
426
+ justifyContent: 'center',
427
+ alignItems: 'stretch',
428
+ flex: 1,
429
+ gap: !hideQRCode && isSmallView ? 0 : 1.5,
430
+ overflow: 'visible',
431
+ px: hideChooseList ? 2 : 0,
432
+ }}>
433
+ {!showStatus && !hideQRCode ? (
434
+ <>
435
+ {qrcodeContent}
436
+ {hideChooseList ? null : (
437
+ <Box>
438
+ <Divider
439
+ orientation="vertical"
440
+ sx={{
441
+ fontSize: 12,
442
+ color: 'text.hint',
443
+ '&::before, &::after': {
444
+ borderColor: 'divider',
445
+ },
446
+ }}>
447
+ or
448
+ </Divider>
449
+ </Box>
450
+ )}
451
+ </>
452
+ ) : null}
453
+ <Box
454
+ sx={{
455
+ display: 'flex',
456
+ flex: 1,
457
+ }}>
458
+ {/* eslint-disable-next-line no-nested-ternary */}
459
+ {content}
460
+ <ConnectProviderList
461
+ slotProps={{
462
+ root: {
463
+ sx: [showStatus ? { display: 'none' } : {}],
464
+ },
465
+ }}
466
+ allowWallet={allowWallet}
467
+ size={hideQRCode && mode !== 'dialog' ? 'normal' : 'small'}
468
+ tokenState={state}
469
+ hideQRCode={hideQRCode}
470
+ messages={messages}
471
+ tokenKey={tokenKey}
472
+ onSuccess={onSuccess}
473
+ passkeyBehavior={passkeyBehavior}
474
+ webWalletUrl={webWalletUrl}
475
+ extraContent={extraContent}
476
+ enabledConnectTypes={enabledConnectTypes}
477
+ onReset={handleReset}
478
+ disableSwitchApp={disableSwitchApp}
479
+ forceUpdate={forceUpdate}
480
+ magicToken={magicToken}
481
+ baseUrl={baseUrl}
482
+ customItems={customItems}
483
+ providerList={providerList}
484
+ qrcode={qrcodeContent}
485
+ />
486
+ </Box>
487
+ </Box>
488
+ );
489
+ let contentResult = fallbackContent;
490
+ if (selectedPlugin) {
491
+ contentResult = selectedPlugin.renderPlaceholder({
492
+ fallback: fallbackContent,
493
+ forceUpdate,
494
+ onSuccess,
495
+ onError,
496
+ });
497
+ } else {
498
+ contentResult = fallbackContent;
499
+ }
500
+
501
+ const containerWidth = hideQRCode || showStatus ? DID_CONNECT_SMALL_WIDTH : DID_CONNECT_MEDIUM_WIDTH;
502
+
503
+ if (loadingProviderList) {
504
+ return <FallbackConnect />;
505
+ }
506
+ return (
507
+ <Box
508
+ ref={rootRef}
509
+ className="did-connect__root"
510
+ sx={{
511
+ backgroundColor: 'background.default',
512
+ display: 'flex',
513
+ flexDirection: 'column',
514
+ height: '100%',
515
+ position: 'relative',
516
+ maxWidth: '100%',
517
+ width:
518
+ // eslint-disable-next-line no-nested-ternary
519
+ mode === 'drawer' ? '100%' : showWalletOptions ? containerWidth - 20 : containerWidth,
520
+ transition: 'width 0.2s ease-in-out',
521
+ margin: 'auto',
522
+ p: 3,
523
+ pb: 0,
524
+ gap: 2.5,
525
+ }}>
526
+ <Box data-did-auth-url={state.url} sx={{ display: 'none' }} />
527
+ <DIDConnectTitle
528
+ title={messages.title}
529
+ description={messages.scan}
530
+ extraContent={extraContent}
531
+ disableSwitchApp={disableSwitchApp}
532
+ showWalletOptions={showWalletOptions}
533
+ onBack={() => setShowWalletOptions(false)}
534
+ />
535
+ <AutoHeight initHeight={24 + 16 + 32}>{contentResult}</AutoHeight>
536
+ <DIDConnectFooter currentAppInfo={currentAppInfo} currentAppColor={currentAppColor} />
537
+ {closeButton}
538
+ </Box>
539
+ );
540
+ }
541
+
542
+ Connect.propTypes = {
543
+ mode: PropTypes.oneOf(['dialog', 'drawer', 'page']),
544
+
545
+ action: PropTypes.string.isRequired,
546
+ baseUrl: PropTypes.string,
547
+ checkFn: PropTypes.func.isRequired,
548
+ checkInterval: PropTypes.number,
549
+ checkTimeout: PropTypes.number,
550
+ // extraParams: PropTypes.object, // 需要使用 useStateContext 中导出的
551
+ prefix: PropTypes.string,
552
+ messages: PropTypes.object,
553
+ tokenKey: PropTypes.string,
554
+ locale: PropTypes.oneOf(['en', 'zh', '']),
555
+ encKey: PropTypes.string,
556
+ autoConnect: PropTypes.bool,
557
+ forceConnected: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
558
+ saveConnect: PropTypes.bool,
559
+ useSocket: PropTypes.bool,
560
+ extraContent: PropTypes.any,
561
+ passkeyBehavior: PropTypes.oneOf(['none', 'both', 'only-existing', 'only-new']),
562
+ enabledConnectTypes: PropTypes.arrayOf(PropTypes.oneOf(['web', 'mobile', ...Object.keys(OAUTH_PROVIDER)])),
563
+ webWalletUrl: PropTypes.string,
564
+
565
+ allowWallet: PropTypes.bool,
566
+ provider: PropTypes.oneOf([LOGIN_PROVIDER.WALLET, ...Object.keys(OAUTH_PROVIDER), '']),
567
+ hideCloseButton: PropTypes.bool,
568
+ disableSwitchApp: PropTypes.bool,
569
+ onClose: PropTypes.func,
570
+ onError: PropTypes.func,
571
+ onSuccess: PropTypes.func,
572
+ onRecreateSession: PropTypes.func,
573
+ setColor: PropTypes.func,
574
+ magicToken: PropTypes.string,
575
+ customItems: PropTypes.arrayOf(PropTypes.node),
576
+ };
577
+
578
+ function WrapConnect({ testOnlyBorderColor = undefined, ...props }) {
579
+ const { checkFn, extraParams = {}, blocklet, masterBlocklet, action, locale = 'en' } = props;
580
+ if (typeof checkFn !== 'function') {
581
+ throw new Error('Cannot initialize did connect component without a fetchFn');
582
+ }
583
+
584
+ return (
585
+ <StateProvider
586
+ blocklet={blocklet}
587
+ masterBlocklet={masterBlocklet}
588
+ action={action}
589
+ locale={locale}
590
+ extraParams={extraParams}
591
+ testOnlyBorderColor={testOnlyBorderColor}
592
+ sx={{
593
+ position: 'relative',
594
+ width: '100%',
595
+ height: '100%',
596
+ lineHeight: 1.2,
597
+ color: 'grey.700',
598
+ '&, & *, & *:before, & *:after': {
599
+ fontFamily: 'Lexend', // 保持跟 DID Wallet 一致
600
+ boxSizing: 'border-box',
601
+ },
602
+ }}>
603
+ <Connect {...props} />
604
+ </StateProvider>
605
+ );
606
+ }
607
+
608
+ WrapConnect.propTypes = {
609
+ checkFn: PropTypes.func.isRequired,
610
+ extraParams: PropTypes.object,
611
+ blocklet: PropTypes.object.isRequired,
612
+ masterBlocklet: PropTypes.object,
613
+ action: PropTypes.string.isRequired,
614
+ locale: PropTypes.string,
615
+ testOnlyBorderColor: PropTypes.string,
616
+ };
617
+
618
+ export default WrapConnect;
@@ -9,6 +9,7 @@ import { translate } from '@arcblock/ux/lib/Locale/util';
9
9
  import { getCurrentApp, getMaster } from '@arcblock/ux/lib/Util/federated';
10
10
  import { getDIDMotifInfo } from '@arcblock/did-motif';
11
11
  import { getDIDColor, isEthereumDid } from '@arcblock/ux/lib/Util';
12
+ import { GA_LAST_LOGIN_METHOD } from '@arcblock/ux/lib/withTracker/constant';
12
13
 
13
14
  import useApps from '../hooks/use-apps';
14
15
  import { SessionContext } from '../../Session/context';
@@ -68,6 +69,13 @@ function StateProvider({
68
69
  sourceAppPid: extraParams?.sourceAppPid,
69
70
  enableSwitchApp: extraParams?.forceSwitch || extraParams?.enableSwitchApp,
70
71
  });
72
+ const lastLoginMethod = useCreation(() => {
73
+ try {
74
+ return localStorage.getItem(GA_LAST_LOGIN_METHOD);
75
+ } catch (error) {
76
+ return '';
77
+ }
78
+ }, []);
71
79
 
72
80
  const rootRef = useRef(null);
73
81
  const size = useSize(rootRef);
@@ -180,6 +188,8 @@ function StateProvider({
180
188
  // 控制钱包登录的显示
181
189
  showWalletOptions,
182
190
  setShowWalletOptions,
191
+ // 用于记录上一次登录方式
192
+ lastLoginMethod,
183
193
  };
184
194
  }, [
185
195
  browser.wallet,
@@ -0,0 +1,47 @@
1
+ import { DIDConnectFooter } from '@arcblock/ux/lib/DIDConnect';
2
+ import { DID_CONNECT_SMALL_WIDTH } from '@arcblock/ux/lib/Util/constant';
3
+ import { getCurrentApp } from '@arcblock/ux/lib/Util/federated';
4
+ import { Box, Skeleton } from '@mui/material';
5
+
6
+ export default function FallbackConnect(props) {
7
+ const currentAppInfo = globalThis.blocklet ? getCurrentApp(globalThis.blocklet) : globalThis.env;
8
+ return (
9
+ <Box
10
+ className="did-connect__root"
11
+ sx={{
12
+ backgroundColor: 'background.default',
13
+ display: 'flex',
14
+ flexDirection: 'column',
15
+ height: '100%',
16
+ position: 'relative',
17
+ maxWidth: '100%',
18
+ // eslint-disable-next-line react/prop-types
19
+ width: props.mode === 'drawer' ? '100%' : DID_CONNECT_SMALL_WIDTH - 20,
20
+ transition: 'width 0.2s ease-in-out',
21
+ margin: 'auto',
22
+ p: 3,
23
+ pb: 0,
24
+ gap: 2.5,
25
+ }}>
26
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
27
+ <Skeleton variant="rounded" height={28} sx={{ width: '6rem' }} />
28
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
29
+ <Skeleton variant="rounded" sx={{ fontSize: '13px', height: '15px' }} />
30
+ <Skeleton variant="rounded" sx={{ fontSize: '13px', height: '15px', width: '3rem' }} />
31
+ </Box>
32
+ </Box>
33
+ <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
34
+ <Skeleton variant="rounded" sx={{ height: '35px' }} />
35
+ <Skeleton variant="rounded" sx={{ height: '35px' }} />
36
+ <Skeleton variant="rounded" sx={{ height: '35px' }} />
37
+ <Box sx={{ display: 'flex', gap: 1.5, mt: 1 }}>
38
+ <Skeleton variant="rounded" sx={{ flex: 1, height: '32px' }} />
39
+ <Skeleton variant="rounded" sx={{ flex: 1, height: '32px' }} />
40
+ <Skeleton variant="rounded" sx={{ flex: 1, height: '32px' }} />
41
+ <Skeleton variant="rounded" sx={{ flex: 1, height: '32px' }} />
42
+ </Box>
43
+ </Box>
44
+ <DIDConnectFooter currentAppInfo={currentAppInfo} />
45
+ </Box>
46
+ );
47
+ }