@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.
- package/LICENSE +13 -0
- package/README.md +134 -0
- package/lib/Address/index.js +4 -0
- package/lib/Avatar/index.js +4 -0
- package/lib/Button/index.js +17 -0
- package/lib/Connect/assets/locale.js +143 -0
- package/lib/Connect/assets/login-bg.png +0 -0
- package/lib/Connect/assets/login-slogan.js +9 -0
- package/lib/Connect/components/action-button.js +26 -0
- package/lib/Connect/components/app-tips.js +132 -0
- package/lib/Connect/components/auto-height.js +31 -0
- package/lib/Connect/components/back-button.js +24 -0
- package/lib/Connect/components/connect-status.js +263 -0
- package/lib/Connect/components/did-connect-title.js +126 -0
- package/lib/Connect/components/download-tips.js +52 -0
- package/lib/Connect/components/loading.js +26 -0
- package/lib/Connect/components/login-item/connect-choose-list.js +249 -0
- package/lib/Connect/components/login-item/login-method-item.js +129 -0
- package/lib/Connect/components/login-item/mobile-login-item.js +114 -0
- package/lib/Connect/components/login-item/passkey-login-item.js +44 -0
- package/lib/Connect/components/login-item/web-login-item.js +97 -0
- package/lib/Connect/components/mask-overlay.js +34 -0
- package/lib/Connect/components/refresh-overlay.js +57 -0
- package/lib/Connect/components/switch-app.js +70 -0
- package/lib/Connect/contexts/state.js +142 -0
- package/lib/Connect/fullpage.js +5 -0
- package/lib/Connect/hooks/auth-url.js +23 -0
- package/lib/Connect/hooks/method-list.js +46 -0
- package/lib/Connect/hooks/page-show.js +17 -0
- package/lib/Connect/hooks/security.js +27 -0
- package/lib/Connect/hooks/token.js +305 -0
- package/lib/Connect/hooks/use-apps.js +19 -0
- package/lib/Connect/hooks/use-quick-connect.js +97 -0
- package/lib/Connect/index.js +498 -0
- package/lib/Connect/landing-page.js +5 -0
- package/lib/Connect/plugins/email/index.js +62 -0
- package/lib/Connect/plugins/email/list-item.js +28 -0
- package/lib/Connect/plugins/email/placeholder.js +283 -0
- package/lib/Connect/plugins/index.js +4 -0
- package/lib/Connect/use-connect.js +164 -0
- package/lib/Connect/with-blocklet.js +15 -0
- package/lib/Connect/with-bridge-call.js +108 -0
- package/lib/Federated/context.js +61 -0
- package/lib/Federated/index.js +7 -0
- package/lib/Logo/index.js +4 -0
- package/lib/OAuth/context.js +234 -0
- package/lib/OAuth/guest.svg.js +5 -0
- package/lib/OAuth/index.js +7 -0
- package/lib/OAuth/passport-switcher.js +114 -0
- package/lib/Passkey/actions.js +165 -0
- package/lib/Passkey/constants.js +4 -0
- package/lib/Passkey/context.js +266 -0
- package/lib/Passkey/dialog.js +277 -0
- package/lib/Passkey/icon.js +13 -0
- package/lib/Passkey/index.js +9 -0
- package/lib/Service/index.js +62 -0
- package/lib/Session/assets/did-spaces-guide-cover.svg.js +135 -0
- package/lib/Session/assets/did-spaces-guide-icon.svg.js +9 -0
- package/lib/Session/context.js +5 -0
- package/lib/Session/did-spaces-guide.js +136 -0
- package/lib/Session/hooks/use-federated.js +64 -0
- package/lib/Session/hooks/use-mobile.js +8 -0
- package/lib/Session/hooks/use-protected-routes.js +11 -0
- package/lib/Session/hooks/use-session-token.js +169 -0
- package/lib/Session/hooks/use-verify.js +45 -0
- package/lib/Session/index.js +896 -0
- package/lib/Session/libs/constants.js +15 -0
- package/lib/Session/libs/did-spaces.js +10 -0
- package/lib/Session/libs/federated.js +42 -0
- package/lib/Session/libs/index.js +15 -0
- package/lib/Session/libs/locales.js +161 -0
- package/lib/Session/libs/login-mobile.js +55 -0
- package/lib/Session/window-focus-aware.js +17 -0
- package/lib/SessionManager/index.js +4 -0
- package/lib/Storage/engine/cookie.js +21 -0
- package/lib/Storage/engine/local-storage.js +36 -0
- package/lib/Storage/index.js +23 -0
- package/lib/User/index.js +6 -0
- package/lib/User/use-did.js +59 -0
- package/lib/User/wrap-did.js +13 -0
- package/lib/WebWalletSWKeeper/index.js +5 -0
- package/lib/constant.js +22 -0
- package/lib/error.js +8 -0
- package/lib/hooks/use-locale.js +7 -0
- package/lib/index.js +33 -0
- package/lib/locales/en.js +17 -0
- package/lib/locales/index.js +10 -0
- package/lib/locales/zh.js +17 -0
- package/lib/package.json.js +7 -0
- package/lib/types.d.ts +355 -0
- package/lib/utils.js +214 -0
- package/package.json +84 -0
- package/src/Address/index.jsx +2 -0
- package/src/Avatar/index.jsx +2 -0
- package/src/Button/Button.stories.jsx +7 -0
- package/src/Button/index.jsx +21 -0
- package/src/Connect/Connect.stories.jsx +34 -0
- package/src/Connect/assets/locale.js +145 -0
- package/src/Connect/assets/login-bg.png +0 -0
- package/src/Connect/assets/login-slogan.js +7 -0
- package/src/Connect/components/action-button.jsx +22 -0
- package/src/Connect/components/app-tips.jsx +156 -0
- package/src/Connect/components/auto-height.jsx +38 -0
- package/src/Connect/components/back-button.jsx +23 -0
- package/src/Connect/components/connect-status.jsx +259 -0
- package/src/Connect/components/did-connect-title.jsx +106 -0
- package/src/Connect/components/download-tips.jsx +55 -0
- package/src/Connect/components/loading.jsx +25 -0
- package/src/Connect/components/login-item/connect-choose-list.jsx +304 -0
- package/src/Connect/components/login-item/login-method-item.jsx +118 -0
- package/src/Connect/components/login-item/mobile-login-item.jsx +179 -0
- package/src/Connect/components/login-item/passkey-login-item.jsx +52 -0
- package/src/Connect/components/login-item/web-login-item.jsx +149 -0
- package/src/Connect/components/mask-overlay.jsx +32 -0
- package/src/Connect/components/refresh-overlay.jsx +52 -0
- package/src/Connect/components/switch-app.jsx +69 -0
- package/src/Connect/contexts/state.jsx +219 -0
- package/src/Connect/fullpage.jsx +3 -0
- package/src/Connect/hooks/auth-url.js +31 -0
- package/src/Connect/hooks/method-list.js +121 -0
- package/src/Connect/hooks/page-show.js +24 -0
- package/src/Connect/hooks/security.js +40 -0
- package/src/Connect/hooks/token.js +639 -0
- package/src/Connect/hooks/use-apps.js +69 -0
- package/src/Connect/hooks/use-quick-connect.js +130 -0
- package/src/Connect/index.jsx +600 -0
- package/src/Connect/landing-page.jsx +3 -0
- package/src/Connect/plugins/email/index.jsx +82 -0
- package/src/Connect/plugins/email/list-item.jsx +31 -0
- package/src/Connect/plugins/email/placeholder.jsx +365 -0
- package/src/Connect/plugins/index.js +2 -0
- package/src/Connect/use-connect.jsx +321 -0
- package/src/Connect/with-blocklet.jsx +26 -0
- package/src/Connect/with-bridge-call.jsx +138 -0
- package/src/Federated/context.jsx +93 -0
- package/src/Federated/index.jsx +1 -0
- package/src/Logo/index.jsx +2 -0
- package/src/OAuth/context.jsx +346 -0
- package/src/OAuth/guest.svg +20 -0
- package/src/OAuth/index.jsx +1 -0
- package/src/OAuth/passport-switcher.jsx +133 -0
- package/src/Passkey/actions.jsx +212 -0
- package/src/Passkey/constants.js +2 -0
- package/src/Passkey/context.jsx +381 -0
- package/src/Passkey/dialog.jsx +391 -0
- package/src/Passkey/icon.jsx +10 -0
- package/src/Passkey/index.jsx +2 -0
- package/src/Service/index.jsx +96 -0
- package/src/Session/assets/did-spaces-guide-cover.svg +128 -0
- package/src/Session/assets/did-spaces-guide-icon.svg +7 -0
- package/src/Session/context.jsx +7 -0
- package/src/Session/did-spaces-guide.jsx +173 -0
- package/src/Session/hooks/use-federated.js +88 -0
- package/src/Session/hooks/use-mobile.jsx +6 -0
- package/src/Session/hooks/use-protected-routes.js +16 -0
- package/src/Session/hooks/use-session-token.js +365 -0
- package/src/Session/hooks/use-verify.jsx +76 -0
- package/src/Session/index.jsx +1687 -0
- package/src/Session/libs/constants.js +14 -0
- package/src/Session/libs/did-spaces.js +38 -0
- package/src/Session/libs/federated.js +79 -0
- package/src/Session/libs/index.js +5 -0
- package/src/Session/libs/locales.js +160 -0
- package/src/Session/libs/login-mobile.js +80 -0
- package/src/Session/window-focus-aware.jsx +28 -0
- package/src/SessionManager/index.jsx +2 -0
- package/src/Storage/engine/cookie.js +23 -0
- package/src/Storage/engine/local-storage.js +55 -0
- package/src/Storage/index.js +25 -0
- package/src/User/index.js +4 -0
- package/src/User/use-did.js +80 -0
- package/src/User/wrap-did.jsx +18 -0
- package/src/WebWalletSWKeeper/index.jsx +3 -0
- package/src/constant.js +26 -0
- package/src/error.js +6 -0
- package/src/hooks/use-locale.jsx +6 -0
- package/src/index.js +43 -0
- package/src/locales/en.jsx +15 -0
- package/src/locales/index.jsx +13 -0
- package/src/locales/zh.jsx +15 -0
- package/src/types.d.ts +355 -0
- package/src/utils.js +395 -0
- package/vite.config.mjs +29 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import { Box } from '@mui/material';
|
|
3
|
+
import { LOGIN_PROVIDER, LOGIN_PROVIDER_NAME } from '@arcblock/ux/lib/Util/constant';
|
|
4
|
+
import { getWebWalletUrl } from '@arcblock/ux/lib/Util/wallet';
|
|
5
|
+
import noop from 'lodash/noop';
|
|
6
|
+
import { useMemoizedFn } from 'ahooks';
|
|
7
|
+
import Cookie from 'js-cookie';
|
|
8
|
+
import { getCookieOptions } from '@arcblock/ux/lib/Util';
|
|
9
|
+
import { useEffect, useRef } from 'react';
|
|
10
|
+
import { mergeSx } from '@arcblock/ux/lib/Util/style';
|
|
11
|
+
import ProviderIcon from '@arcblock/ux/lib/DIDConnect/provider-icon';
|
|
12
|
+
|
|
13
|
+
import MobileLoginItem from './mobile-login-item';
|
|
14
|
+
import WebLoginItem from './web-login-item';
|
|
15
|
+
import LoginMethodItem from './login-method-item';
|
|
16
|
+
import { useOAuth } from '../../../OAuth';
|
|
17
|
+
import { useStateContext } from '../../contexts/state';
|
|
18
|
+
import { getApiErrorMessage, getAppId, logger } from '../../../utils';
|
|
19
|
+
import PasskeyLoginItem from './passkey-login-item';
|
|
20
|
+
import { usePasskey } from '../../../Passkey';
|
|
21
|
+
import { useEmailPlugin } from '../../plugins';
|
|
22
|
+
|
|
23
|
+
export default function ConnectChooseList({
|
|
24
|
+
onSuccess = noop,
|
|
25
|
+
onError = noop,
|
|
26
|
+
size = 'small',
|
|
27
|
+
tokenState,
|
|
28
|
+
webWalletUrl = getWebWalletUrl(),
|
|
29
|
+
tokenKey,
|
|
30
|
+
passkeyBehavior = 'none',
|
|
31
|
+
onRest = noop,
|
|
32
|
+
showMobileLogin = true,
|
|
33
|
+
showOAuthLogin = true,
|
|
34
|
+
showPasskeyLogin = true,
|
|
35
|
+
showWebLogin = true,
|
|
36
|
+
showEmailLogin = true,
|
|
37
|
+
oauthProviderList = [],
|
|
38
|
+
slotProps = {},
|
|
39
|
+
disableSwitchApp = false,
|
|
40
|
+
forceUpdate = noop,
|
|
41
|
+
magicToken = undefined,
|
|
42
|
+
baseUrl = '/',
|
|
43
|
+
customItems = [],
|
|
44
|
+
}) {
|
|
45
|
+
const walletLoginRef = useRef(null);
|
|
46
|
+
const webLoginRef = useRef(null);
|
|
47
|
+
const passkeyLoginRef = useRef(null);
|
|
48
|
+
const { loginOAuth, logoutOAuth, t, oauthState } = useOAuth();
|
|
49
|
+
const { passkeyState } = usePasskey();
|
|
50
|
+
const { extraParams, locale, connectState, plugins, setPlugins, setSelectedPlugin, getPlugin } = useStateContext();
|
|
51
|
+
|
|
52
|
+
const handleLoginOAuth = useMemoizedFn(async (item) => {
|
|
53
|
+
tokenState.reset();
|
|
54
|
+
oauthState.reset({
|
|
55
|
+
status: 'scanned',
|
|
56
|
+
});
|
|
57
|
+
passkeyState.reset();
|
|
58
|
+
connectState.chooseMethod = item.provider;
|
|
59
|
+
|
|
60
|
+
const sourceAppPid = extraParams?.sourceAppPid;
|
|
61
|
+
try {
|
|
62
|
+
oauthState.loading = true;
|
|
63
|
+
oauthState.status = 'scanned';
|
|
64
|
+
const loginResult = await loginOAuth(item, {
|
|
65
|
+
action: tokenState.action,
|
|
66
|
+
...extraParams,
|
|
67
|
+
});
|
|
68
|
+
const cookieOptions = getCookieOptions({ returnDomain: false });
|
|
69
|
+
Cookie.remove('connected_did', cookieOptions);
|
|
70
|
+
Cookie.remove('connected_pk', cookieOptions);
|
|
71
|
+
Cookie.remove('connected_wallet_os', cookieOptions);
|
|
72
|
+
|
|
73
|
+
if (loginResult?.sessionToken) {
|
|
74
|
+
await onSuccess(
|
|
75
|
+
{
|
|
76
|
+
...loginResult,
|
|
77
|
+
encrypted: false,
|
|
78
|
+
},
|
|
79
|
+
(val) => val,
|
|
80
|
+
{
|
|
81
|
+
sourceAppPid,
|
|
82
|
+
connected_app: getAppId(tokenState.appInfo, tokenState.memberAppInfo),
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
oauthState.loading = false;
|
|
86
|
+
oauthState.status = 'succeed';
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
logger.error(`Failed login OAuth: ${item.provider}`, e);
|
|
90
|
+
const errorMessage = getApiErrorMessage(e, t('loginOAuthFailed'));
|
|
91
|
+
oauthState.loading = false;
|
|
92
|
+
oauthState.error = errorMessage;
|
|
93
|
+
oauthState.status = 'error';
|
|
94
|
+
await logoutOAuth({ provider: item.provider });
|
|
95
|
+
onError(new Error(errorMessage));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const defaultRetryConnect = useMemoizedFn(async () => {
|
|
100
|
+
tokenState.reset();
|
|
101
|
+
await onRest();
|
|
102
|
+
tokenState.status = 'created';
|
|
103
|
+
connectState.chooseMethod = 'wallet';
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const emailPlugin = useEmailPlugin({ baseUrl });
|
|
107
|
+
|
|
108
|
+
const setupMagicToken = useMemoizedFn(() => {
|
|
109
|
+
if (magicToken && showEmailLogin && plugins.some((plugin) => plugin.name === LOGIN_PROVIDER.EMAIL)) {
|
|
110
|
+
const plugin = getPlugin(LOGIN_PROVIDER.EMAIL);
|
|
111
|
+
if (plugin.state.status === 'idle') {
|
|
112
|
+
plugin.state.reset();
|
|
113
|
+
plugin.state.magicToken = magicToken;
|
|
114
|
+
connectState.chooseMethod = LOGIN_PROVIDER.EMAIL;
|
|
115
|
+
setSelectedPlugin(plugin);
|
|
116
|
+
forceUpdate();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// 考虑到切换账号的情况,此时 showEmailLogin 会根据当前上下文的 blocklet 发生变化,所以需要监听 showEmailLogin 的变化
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
const finalList = [];
|
|
124
|
+
if (showEmailLogin) {
|
|
125
|
+
// 尽可能确保在 baseUrl 不变的情况下,不更改 email 插件的数据,以确保页面的展示不会出现闪烁
|
|
126
|
+
const prevEmailPlugin = getPlugin(LOGIN_PROVIDER.EMAIL);
|
|
127
|
+
if (prevEmailPlugin && prevEmailPlugin.baseUrl === emailPlugin.baseUrl) {
|
|
128
|
+
finalList.push(prevEmailPlugin);
|
|
129
|
+
} else {
|
|
130
|
+
finalList.push(emailPlugin);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
setPlugins(finalList);
|
|
134
|
+
connectState.retryConnect = defaultRetryConnect;
|
|
135
|
+
// HACK: 必须要设置延迟,不然拿不到最新的 plugins 的值
|
|
136
|
+
setTimeout(() => {
|
|
137
|
+
setupMagicToken();
|
|
138
|
+
}, 100);
|
|
139
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
140
|
+
}, [showEmailLogin]);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Box className="did-connect__choose" sx={mergeSx({ flex: 1 }, slotProps?.root?.sx)}>
|
|
144
|
+
<Box
|
|
145
|
+
sx={{
|
|
146
|
+
display: 'flex',
|
|
147
|
+
flexDirection: 'column',
|
|
148
|
+
gap: 2,
|
|
149
|
+
}}>
|
|
150
|
+
<Box
|
|
151
|
+
sx={[
|
|
152
|
+
{
|
|
153
|
+
display: 'flex',
|
|
154
|
+
flexDirection: 'column',
|
|
155
|
+
gap: 1.5,
|
|
156
|
+
},
|
|
157
|
+
]}>
|
|
158
|
+
{showMobileLogin && size !== 'small' && (
|
|
159
|
+
<MobileLoginItem
|
|
160
|
+
ref={walletLoginRef}
|
|
161
|
+
tokenState={tokenState}
|
|
162
|
+
sx={[size === 'small' ? { p: 1 } : { p: 2 }]}
|
|
163
|
+
locale={locale}
|
|
164
|
+
tokenKey={tokenKey}
|
|
165
|
+
disableSwitchApp={disableSwitchApp}
|
|
166
|
+
onClick={async () => {
|
|
167
|
+
tokenState.reset();
|
|
168
|
+
await onRest();
|
|
169
|
+
tokenState.status = 'created';
|
|
170
|
+
connectState.chooseMethod = 'wallet';
|
|
171
|
+
const connectFn = walletLoginRef.current?.connect;
|
|
172
|
+
connectState.retryConnect = () => {
|
|
173
|
+
connectFn(defaultRetryConnect);
|
|
174
|
+
};
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
{showWebLogin && (
|
|
179
|
+
<WebLoginItem
|
|
180
|
+
ref={webLoginRef}
|
|
181
|
+
tokenState={tokenState}
|
|
182
|
+
webWalletUrl={webWalletUrl}
|
|
183
|
+
sx={[size === 'small' ? { p: 1 } : { p: 2 }]}
|
|
184
|
+
disableSwitchApp={disableSwitchApp}
|
|
185
|
+
onClick={() => {
|
|
186
|
+
// HACK: 在点击插件登录时,直接将状态置为扫码中,避免插件登录时的状态切换
|
|
187
|
+
tokenState.status = 'scanned';
|
|
188
|
+
connectState.chooseMethod = 'wallet-web';
|
|
189
|
+
const connectFn = webLoginRef.current.connect;
|
|
190
|
+
connectState.retryConnect = async () => {
|
|
191
|
+
await onRest();
|
|
192
|
+
tokenState.error = '';
|
|
193
|
+
tokenState.status = 'scanned';
|
|
194
|
+
connectFn();
|
|
195
|
+
};
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
199
|
+
|
|
200
|
+
{showOAuthLogin || showPasskeyLogin || showEmailLogin ? (
|
|
201
|
+
<Box
|
|
202
|
+
sx={[
|
|
203
|
+
{
|
|
204
|
+
display: 'flex',
|
|
205
|
+
flexDirection: 'column',
|
|
206
|
+
gap: 1.5,
|
|
207
|
+
},
|
|
208
|
+
]}>
|
|
209
|
+
{/* TODO: @zhanghan 需要将所有 method 转换为 plugin 的形式注入 */}
|
|
210
|
+
{plugins.map((plugin) =>
|
|
211
|
+
plugin.renderListItem({
|
|
212
|
+
key: plugin.name,
|
|
213
|
+
sx: [size === 'small' ? { p: 1 } : { p: 2 }],
|
|
214
|
+
// forceUpdate,
|
|
215
|
+
})
|
|
216
|
+
)}
|
|
217
|
+
{showOAuthLogin
|
|
218
|
+
? oauthProviderList.map((item) => (
|
|
219
|
+
<LoginMethodItem
|
|
220
|
+
key={item.provider}
|
|
221
|
+
title={LOGIN_PROVIDER_NAME[item.provider]}
|
|
222
|
+
icon={
|
|
223
|
+
<ProviderIcon
|
|
224
|
+
provider={item.provider}
|
|
225
|
+
sx={{
|
|
226
|
+
transform: 'scale(0.95)',
|
|
227
|
+
width: 24,
|
|
228
|
+
height: 24,
|
|
229
|
+
color: 'text.primary',
|
|
230
|
+
}}
|
|
231
|
+
/>
|
|
232
|
+
}
|
|
233
|
+
onClick={() => {
|
|
234
|
+
handleLoginOAuth(item);
|
|
235
|
+
connectState.retryConnect = () => {
|
|
236
|
+
handleLoginOAuth(item);
|
|
237
|
+
};
|
|
238
|
+
}}
|
|
239
|
+
sx={[size === 'small' ? { p: 1 } : { p: 2 }]}
|
|
240
|
+
/>
|
|
241
|
+
))
|
|
242
|
+
: null}
|
|
243
|
+
{showPasskeyLogin ? (
|
|
244
|
+
<PasskeyLoginItem
|
|
245
|
+
ref={passkeyLoginRef}
|
|
246
|
+
onSuccess={onSuccess}
|
|
247
|
+
onError={onError}
|
|
248
|
+
tokenState={tokenState}
|
|
249
|
+
behavior={passkeyBehavior}
|
|
250
|
+
sx={[size === 'small' ? { p: 1 } : { p: 2 }]}
|
|
251
|
+
onClick={() => {
|
|
252
|
+
const connectFn = passkeyLoginRef.current.connect;
|
|
253
|
+
connectState.chooseMethod = 'passkey';
|
|
254
|
+
connectState.retryConnect = () => {
|
|
255
|
+
passkeyState.verifying = true;
|
|
256
|
+
connectState.chooseMethod = 'passkey';
|
|
257
|
+
connectFn();
|
|
258
|
+
};
|
|
259
|
+
}}
|
|
260
|
+
slotProps={{
|
|
261
|
+
icon: {
|
|
262
|
+
sx: {
|
|
263
|
+
fontSize: 24,
|
|
264
|
+
'& svg': {
|
|
265
|
+
fontSize: 24,
|
|
266
|
+
width: '1em',
|
|
267
|
+
height: '1em',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
}}
|
|
272
|
+
/>
|
|
273
|
+
) : null}
|
|
274
|
+
</Box>
|
|
275
|
+
) : null}
|
|
276
|
+
{customItems.map((item) => (!item ? null : item))}
|
|
277
|
+
</Box>
|
|
278
|
+
</Box>
|
|
279
|
+
</Box>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
ConnectChooseList.propTypes = {
|
|
284
|
+
onSuccess: PropTypes.func,
|
|
285
|
+
onError: PropTypes.func,
|
|
286
|
+
size: PropTypes.oneOf(['small', 'normal', 'large']),
|
|
287
|
+
tokenState: PropTypes.object.isRequired,
|
|
288
|
+
webWalletUrl: PropTypes.string,
|
|
289
|
+
tokenKey: PropTypes.string.isRequired,
|
|
290
|
+
passkeyBehavior: PropTypes.oneOf(['none', 'both', 'only-existing', 'only-new']),
|
|
291
|
+
onRest: PropTypes.func,
|
|
292
|
+
showMobileLogin: PropTypes.bool,
|
|
293
|
+
showOAuthLogin: PropTypes.bool,
|
|
294
|
+
showPasskeyLogin: PropTypes.bool,
|
|
295
|
+
showWebLogin: PropTypes.bool,
|
|
296
|
+
showEmailLogin: PropTypes.bool,
|
|
297
|
+
oauthProviderList: PropTypes.array,
|
|
298
|
+
slotProps: PropTypes.object,
|
|
299
|
+
disableSwitchApp: PropTypes.bool,
|
|
300
|
+
forceUpdate: PropTypes.func,
|
|
301
|
+
magicToken: PropTypes.string,
|
|
302
|
+
baseUrl: PropTypes.string,
|
|
303
|
+
customItems: PropTypes.arrayOf(PropTypes.node),
|
|
304
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Box, Typography, useTheme } from '@mui/material';
|
|
2
|
+
import { Icon } from '@iconify/react';
|
|
3
|
+
import { isValidElement } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
import { mergeSx } from '@arcblock/ux/lib/Util/style';
|
|
6
|
+
import ArrowRightAltRoundedIcon from '@iconify-icons/material-symbols/arrow-right-alt-rounded';
|
|
7
|
+
|
|
8
|
+
export default function LoginMethodItem({
|
|
9
|
+
title,
|
|
10
|
+
description = null,
|
|
11
|
+
icon,
|
|
12
|
+
iconScale = 0.95,
|
|
13
|
+
slotProps = {},
|
|
14
|
+
mode = 'normal',
|
|
15
|
+
...rest
|
|
16
|
+
}) {
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Box
|
|
21
|
+
{...rest}
|
|
22
|
+
sx={mergeSx(
|
|
23
|
+
{
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
gap: 1,
|
|
27
|
+
cursor: 'pointer',
|
|
28
|
+
p: 1,
|
|
29
|
+
borderRadius: 1,
|
|
30
|
+
backgroundColor: 'grey.50',
|
|
31
|
+
textDecoration: 'none',
|
|
32
|
+
transition: 'background-color 0.5s',
|
|
33
|
+
'&:hover, &:active, &.did-connect__choose-item__active': {
|
|
34
|
+
backgroundColor: 'grey.100',
|
|
35
|
+
},
|
|
36
|
+
'& .other-item-icon': {
|
|
37
|
+
opacity: '0',
|
|
38
|
+
transform: 'translateX(-100%)',
|
|
39
|
+
transition: 'transform 0.2s ease, opacity 0.1s ease',
|
|
40
|
+
},
|
|
41
|
+
'&:hover': {
|
|
42
|
+
'& .other-item-icon': {
|
|
43
|
+
display: 'inline-block',
|
|
44
|
+
transform: 'translateX(0)',
|
|
45
|
+
opacity: '1',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
rest?.sx
|
|
50
|
+
)}>
|
|
51
|
+
<Box
|
|
52
|
+
className="arc-login-item__icon"
|
|
53
|
+
sx={mergeSx(
|
|
54
|
+
{
|
|
55
|
+
display: 'flex',
|
|
56
|
+
justifyContent: 'center',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
color: 'text.primary',
|
|
59
|
+
},
|
|
60
|
+
slotProps?.icon?.sx
|
|
61
|
+
)}>
|
|
62
|
+
{isValidElement(icon) ? (
|
|
63
|
+
icon
|
|
64
|
+
) : (
|
|
65
|
+
<Box
|
|
66
|
+
component={Icon}
|
|
67
|
+
icon={icon}
|
|
68
|
+
sx={{
|
|
69
|
+
transform: `scale(${iconScale})`,
|
|
70
|
+
width: 24,
|
|
71
|
+
height: 24,
|
|
72
|
+
}}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
</Box>
|
|
76
|
+
{mode === 'normal' ? (
|
|
77
|
+
<>
|
|
78
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
|
|
79
|
+
<Typography
|
|
80
|
+
sx={{
|
|
81
|
+
fontSize: 14,
|
|
82
|
+
fontWeight: '500',
|
|
83
|
+
color: 'text.primary',
|
|
84
|
+
whiteSpace: 'nowrap',
|
|
85
|
+
}}>
|
|
86
|
+
{title}
|
|
87
|
+
</Typography>
|
|
88
|
+
{description ? (
|
|
89
|
+
<Typography
|
|
90
|
+
sx={{
|
|
91
|
+
color: 'text.secondary',
|
|
92
|
+
fontSize: 12,
|
|
93
|
+
lineHeight: 1,
|
|
94
|
+
}}>
|
|
95
|
+
{description}
|
|
96
|
+
</Typography>
|
|
97
|
+
) : null}
|
|
98
|
+
</Box>
|
|
99
|
+
<Icon
|
|
100
|
+
className="other-item-icon"
|
|
101
|
+
icon={ArrowRightAltRoundedIcon}
|
|
102
|
+
fontSize="1.3rem"
|
|
103
|
+
color={theme.palette.primary.main}
|
|
104
|
+
/>
|
|
105
|
+
</>
|
|
106
|
+
) : null}
|
|
107
|
+
</Box>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
LoginMethodItem.propTypes = {
|
|
112
|
+
title: PropTypes.string.isRequired,
|
|
113
|
+
description: PropTypes.string,
|
|
114
|
+
icon: PropTypes.any.isRequired,
|
|
115
|
+
iconScale: PropTypes.number,
|
|
116
|
+
slotProps: PropTypes.object,
|
|
117
|
+
mode: PropTypes.oneOf(['simple', 'normal']),
|
|
118
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import { CircularProgress } from '@mui/material';
|
|
3
|
+
import { useCreation, useMemoizedFn, useReactive } from 'ahooks';
|
|
4
|
+
import bridge from '@arcblock/bridge';
|
|
5
|
+
import { useBrowser } from '@arcblock/react-hooks';
|
|
6
|
+
import noop from 'lodash/noop';
|
|
7
|
+
import { useImperativeHandle } from 'react';
|
|
8
|
+
import { mergeSx } from '@arcblock/ux/lib/Util/style';
|
|
9
|
+
import ProviderIcon from '@arcblock/ux/lib/DIDConnect/provider-icon';
|
|
10
|
+
import { LOGIN_PROVIDER } from '@arcblock/ux/lib/Util/constant';
|
|
11
|
+
|
|
12
|
+
import { useStateContext } from '../../contexts/state';
|
|
13
|
+
import LoginMethodItem from './login-method-item';
|
|
14
|
+
import { logger } from '../../../utils';
|
|
15
|
+
import useAuthUrl from '../../hooks/auth-url';
|
|
16
|
+
|
|
17
|
+
export default function MobileLoginItem({
|
|
18
|
+
ref = null,
|
|
19
|
+
tokenState,
|
|
20
|
+
locale,
|
|
21
|
+
tokenKey,
|
|
22
|
+
onClick = noop,
|
|
23
|
+
disableSwitchApp = false,
|
|
24
|
+
...rest
|
|
25
|
+
}) {
|
|
26
|
+
const currentState = useReactive({
|
|
27
|
+
loading: false,
|
|
28
|
+
});
|
|
29
|
+
const { isWalletWebview } = useStateContext();
|
|
30
|
+
const title = 'DID Wallet';
|
|
31
|
+
const browser = useBrowser();
|
|
32
|
+
|
|
33
|
+
const callbackUrl = new URL(window.location.href);
|
|
34
|
+
const urlWithParams = useAuthUrl({ disableSwitchApp, tokenState });
|
|
35
|
+
|
|
36
|
+
const deepLink = useCreation(() => {
|
|
37
|
+
if (!tokenState.url) {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
callbackUrl.searchParams.set('tokenKey', tokenState.token);
|
|
42
|
+
|
|
43
|
+
const link = new URL(urlWithParams);
|
|
44
|
+
if (!browser.wallet) {
|
|
45
|
+
link.searchParams.set('callback', encodeURIComponent(callbackUrl));
|
|
46
|
+
link.searchParams.set('callback_delay', 1500);
|
|
47
|
+
}
|
|
48
|
+
link.searchParams.set('locale', locale);
|
|
49
|
+
const encoder = new TextEncoder();
|
|
50
|
+
const currentUrlLength = link.href.length;
|
|
51
|
+
|
|
52
|
+
let appInfo = '';
|
|
53
|
+
let memberAppInfo = '';
|
|
54
|
+
try {
|
|
55
|
+
if (tokenState.appInfo) {
|
|
56
|
+
const encoded = encoder.encode(JSON.stringify(tokenState.appInfo));
|
|
57
|
+
appInfo = btoa(String.fromCharCode(...encoded));
|
|
58
|
+
}
|
|
59
|
+
if (tokenState.memberAppInfo) {
|
|
60
|
+
const encoded = encoder.encode(JSON.stringify(tokenState.memberAppInfo));
|
|
61
|
+
memberAppInfo = btoa(String.fromCharCode(...encoded));
|
|
62
|
+
}
|
|
63
|
+
// NOTICE: 当长度过长时,不再添加 appInfo & memberAppInfo
|
|
64
|
+
if (
|
|
65
|
+
currentUrlLength + 'appInfo='.length + appInfo.length + 'memberAppInfo='.length + memberAppInfo.length >
|
|
66
|
+
2000
|
|
67
|
+
) {
|
|
68
|
+
logger.warn('URL too long, drop appInfo & memberAppInfo', { appInfo, memberAppInfo });
|
|
69
|
+
throw new Error('URL too long');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (memberAppInfo) {
|
|
73
|
+
if (appInfo) {
|
|
74
|
+
// 在有 memberAppInfo 的情况下,两个值必须一起写入
|
|
75
|
+
link.searchParams.set('appInfo', appInfo);
|
|
76
|
+
link.searchParams.set('memberAppInfo', memberAppInfo);
|
|
77
|
+
}
|
|
78
|
+
} else if (appInfo) {
|
|
79
|
+
link.searchParams.set('appInfo', appInfo);
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
logger.warn('Failed to convert appInfo & memberAppInfo', error);
|
|
83
|
+
}
|
|
84
|
+
return link.href.replace(/^https?:\/\//, 'abt://');
|
|
85
|
+
}, [tokenKey, tokenState.token, tokenState.appInfo, tokenState.memberAppInfo, tokenState.url]);
|
|
86
|
+
|
|
87
|
+
const handleWalletConnect = useMemoizedFn(() => {
|
|
88
|
+
currentState.loading = true;
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
bridge.call('authAction', { action: 'auth', deepLink });
|
|
91
|
+
}, 600);
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
currentState.loading = false;
|
|
94
|
+
}, 2000);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 此处用于在 mobile 浏览器中,用户在点击打开按钮时,直接渲染 scanned 的样式(解决 safari 在页面隐藏后,无法正常发起请求带来的体验较差)
|
|
98
|
+
// 需要在页面再次可见时,将状态设置为正确的值
|
|
99
|
+
const handleOpenDeeplink = useMemoizedFn(() => {
|
|
100
|
+
tokenState.checking = true;
|
|
101
|
+
tokenState.status = 'scanned';
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const modifyProps = {};
|
|
105
|
+
if (!isWalletWebview && browser.mobile.any) {
|
|
106
|
+
modifyProps.component = 'a';
|
|
107
|
+
modifyProps.href = deepLink;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!tokenState.url) {
|
|
111
|
+
modifyProps.sx = {
|
|
112
|
+
cursor: 'not-allowed',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const handleConnectFn = useMemoizedFn((fallback) => {
|
|
117
|
+
if (!tokenState.url) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (isWalletWebview) {
|
|
122
|
+
handleWalletConnect();
|
|
123
|
+
} else if (browser.mobile.any) {
|
|
124
|
+
handleOpenDeeplink();
|
|
125
|
+
} else {
|
|
126
|
+
fallback();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const handleConnect = useMemoizedFn(() => {
|
|
131
|
+
handleConnectFn(onClick);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
useImperativeHandle(ref, () => ({
|
|
135
|
+
connect: handleConnectFn,
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<LoginMethodItem
|
|
140
|
+
{...rest}
|
|
141
|
+
{...modifyProps}
|
|
142
|
+
sx={mergeSx(rest?.sx, modifyProps?.sx)}
|
|
143
|
+
title={title}
|
|
144
|
+
icon={
|
|
145
|
+
currentState.loading || !tokenState.url ? (
|
|
146
|
+
<CircularProgress
|
|
147
|
+
color="primary"
|
|
148
|
+
size={24}
|
|
149
|
+
sx={{
|
|
150
|
+
'& svg': {
|
|
151
|
+
transform: 'scale(0.75)',
|
|
152
|
+
},
|
|
153
|
+
}}
|
|
154
|
+
/>
|
|
155
|
+
) : (
|
|
156
|
+
<ProviderIcon
|
|
157
|
+
width="24px"
|
|
158
|
+
height="24px"
|
|
159
|
+
provider={LOGIN_PROVIDER.DID_WALLET}
|
|
160
|
+
style={{
|
|
161
|
+
transform: 'scale(0.9)',
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
iconScale={1}
|
|
167
|
+
onClick={handleConnect}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
MobileLoginItem.propTypes = {
|
|
173
|
+
tokenState: PropTypes.object.isRequired,
|
|
174
|
+
tokenKey: PropTypes.string.isRequired,
|
|
175
|
+
locale: PropTypes.string.isRequired,
|
|
176
|
+
onClick: PropTypes.func,
|
|
177
|
+
disableSwitchApp: PropTypes.bool,
|
|
178
|
+
ref: PropTypes.any,
|
|
179
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import { useImperativeHandle, useRef } from 'react';
|
|
3
|
+
import { useMemoizedFn } from 'ahooks';
|
|
4
|
+
|
|
5
|
+
import PasskeyActions from '../../../Passkey/actions';
|
|
6
|
+
import { getAppId } from '../../../utils';
|
|
7
|
+
import { useStateContext } from '../../contexts/state';
|
|
8
|
+
|
|
9
|
+
export default function PasskeyLoginItem({ ref = null, onSuccess, tokenState, behavior = 'none', ...rest }) {
|
|
10
|
+
const { extraParams } = useStateContext();
|
|
11
|
+
const passkeyActionRef = useRef(null);
|
|
12
|
+
|
|
13
|
+
const handleSuccess = useMemoizedFn((result) =>
|
|
14
|
+
onSuccess(
|
|
15
|
+
{
|
|
16
|
+
...result,
|
|
17
|
+
encrypted: false,
|
|
18
|
+
},
|
|
19
|
+
(val) => val,
|
|
20
|
+
{
|
|
21
|
+
sourceAppPid: extraParams?.sourceAppPid,
|
|
22
|
+
connected_app: getAppId(tokenState.appInfo, tokenState.memberAppInfo),
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
useImperativeHandle(ref, () => ({
|
|
28
|
+
connect: passkeyActionRef.current.click,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
if (behavior === 'none') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<PasskeyActions
|
|
37
|
+
{...rest}
|
|
38
|
+
ref={passkeyActionRef}
|
|
39
|
+
action={tokenState.action}
|
|
40
|
+
behavior={behavior}
|
|
41
|
+
onSuccess={handleSuccess}
|
|
42
|
+
extraParams={extraParams}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
PasskeyLoginItem.propTypes = {
|
|
48
|
+
onSuccess: PropTypes.func.isRequired,
|
|
49
|
+
tokenState: PropTypes.object.isRequired,
|
|
50
|
+
behavior: PropTypes.oneOf(['none', 'both', 'only-existing', 'only-new']),
|
|
51
|
+
ref: PropTypes.any,
|
|
52
|
+
};
|