@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,365 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useCreation, useInterval, useMemoizedFn } from 'ahooks';
|
|
3
|
+
import { joinURL, withQuery } from 'ufo';
|
|
4
|
+
import Cookie from 'js-cookie';
|
|
5
|
+
import { getCookieOptions, setVisitorId } from '@arcblock/ux/lib/Util';
|
|
6
|
+
import jwtDecode from 'jwt-decode';
|
|
7
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
8
|
+
import pRetry from 'p-retry';
|
|
9
|
+
import noop from 'lodash/noop';
|
|
10
|
+
|
|
11
|
+
import createService from '../../Service';
|
|
12
|
+
import { createAxios, getBrowserLang, decrypt as _decrypt, sleep, logger, debug } from '../../utils';
|
|
13
|
+
import { CHECK_INTERVAL_TIME, LANG_COOKIE_NAME } from '../../constant';
|
|
14
|
+
|
|
15
|
+
class ErrorToken extends Error {}
|
|
16
|
+
|
|
17
|
+
const MAX_RETRY_COUNT = 10;
|
|
18
|
+
|
|
19
|
+
const decrypt = (v) => _decrypt(v, localStorage.getItem('__encKey'), localStorage.getItem('__decKey'));
|
|
20
|
+
|
|
21
|
+
export default function useSessionToken({
|
|
22
|
+
state,
|
|
23
|
+
pageState,
|
|
24
|
+
serviceHost,
|
|
25
|
+
sessionTokenStorage,
|
|
26
|
+
refreshTokenStorage,
|
|
27
|
+
lazyRefreshToken,
|
|
28
|
+
apiOptions = {},
|
|
29
|
+
onRefresh = noop,
|
|
30
|
+
}) {
|
|
31
|
+
const [retryCount, setRetryCount] = useState(0);
|
|
32
|
+
const [intervalTime, setIntervalTime] = useState(CHECK_INTERVAL_TIME);
|
|
33
|
+
const { getToken: getSessionToken, setToken: setSessionToken, removeToken: removeSessionToken } = sessionTokenStorage;
|
|
34
|
+
const { getToken: getRefreshToken, setToken: setRefreshToken, removeToken: removeRefreshToken } = refreshTokenStorage;
|
|
35
|
+
const request = useCreation(() => {
|
|
36
|
+
return createAxios({
|
|
37
|
+
baseURL: serviceHost,
|
|
38
|
+
timeout: 10 * 1000,
|
|
39
|
+
secure: true,
|
|
40
|
+
});
|
|
41
|
+
}, [serviceHost]);
|
|
42
|
+
const removeToken = () => {
|
|
43
|
+
removeSessionToken();
|
|
44
|
+
removeRefreshToken();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const service = useCreation(() => {
|
|
48
|
+
return createService(
|
|
49
|
+
{
|
|
50
|
+
sessionTokenStorage,
|
|
51
|
+
refreshTokenStorage,
|
|
52
|
+
serviceHost,
|
|
53
|
+
authServicePrefix: pageState.prefix,
|
|
54
|
+
onRefreshTokenError() {
|
|
55
|
+
removeToken();
|
|
56
|
+
// HACK: 在使用 useReactive 后,即使重复的给子字段赋值为 null,也会引起组件的重渲染,所以对于定时循环来说,一定要避免这一点,防止无效的赋值引起页面重渲染
|
|
57
|
+
if (state.user) {
|
|
58
|
+
state.user = null;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
apiOptions
|
|
63
|
+
);
|
|
64
|
+
}, [serviceHost, pageState.prefix]);
|
|
65
|
+
|
|
66
|
+
const clearSession = () => {
|
|
67
|
+
const cookieOptions = getCookieOptions({ returnDomain: false });
|
|
68
|
+
Cookie.remove('connected_did', cookieOptions);
|
|
69
|
+
Cookie.remove('connected_pk', cookieOptions);
|
|
70
|
+
Cookie.remove('connected_app', cookieOptions);
|
|
71
|
+
Cookie.remove('connected_wallet_os', cookieOptions);
|
|
72
|
+
removeToken();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const checkCookieLocale = useMemoizedFn(() => {
|
|
76
|
+
const latestLocale = Cookie.get(LANG_COOKIE_NAME) || getBrowserLang();
|
|
77
|
+
if (latestLocale !== pageState.currentLocale) {
|
|
78
|
+
pageState.currentLocale = latestLocale;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const handleGetUnreadCount = useMemoizedFn(async () => {
|
|
83
|
+
const requestFn = () => service.get(joinURL(pageState.notificationPrefix, '/unread-count'));
|
|
84
|
+
try {
|
|
85
|
+
const { data } = await requestFn();
|
|
86
|
+
return data.unReadCount;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('getUnreadCount error', err);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const _refresh = useMemoizedFn(
|
|
94
|
+
async ({ showProgress = false, requestFn = null, onlyRefreshToken = false, type = 'refreshToken' }) => {
|
|
95
|
+
try {
|
|
96
|
+
if (state.loading) {
|
|
97
|
+
console.warn('SessionProvider.refresh is currently in progress, call it will be noop');
|
|
98
|
+
return { loading: true };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (showProgress) {
|
|
102
|
+
state.loading = true;
|
|
103
|
+
}
|
|
104
|
+
const { data, status } = await requestFn();
|
|
105
|
+
|
|
106
|
+
if (status === 400) {
|
|
107
|
+
// NOTE: 如果通过多种方式传递了 token,服务端会报 400 的错误,需要移除现有的 token
|
|
108
|
+
removeToken();
|
|
109
|
+
state.user = null;
|
|
110
|
+
state.error = '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (data.error) {
|
|
114
|
+
// Some thing went wrong
|
|
115
|
+
state.error = data.error;
|
|
116
|
+
state.open = false;
|
|
117
|
+
state.unReadCount = 0;
|
|
118
|
+
throw new Error(data.error);
|
|
119
|
+
} else if (data.user) {
|
|
120
|
+
// 当获取到 user 后再去请求 unreadCount
|
|
121
|
+
try {
|
|
122
|
+
const count = await handleGetUnreadCount();
|
|
123
|
+
state.unReadCount = count || 0;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logger.error('getUnreadCount error', err);
|
|
126
|
+
state.unReadCount = 0;
|
|
127
|
+
}
|
|
128
|
+
// We have valid user
|
|
129
|
+
// 用于 refreshSession 的逻辑
|
|
130
|
+
if (data.nextToken) {
|
|
131
|
+
setSessionToken(data.nextToken);
|
|
132
|
+
onRefresh({
|
|
133
|
+
type,
|
|
134
|
+
sessionToken: data.nextToken,
|
|
135
|
+
refreshToken: data.nextRefreshToken,
|
|
136
|
+
user: data.user,
|
|
137
|
+
});
|
|
138
|
+
if (data.nextRefreshToken) {
|
|
139
|
+
setRefreshToken(data.nextRefreshToken);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!onlyRefreshToken) {
|
|
143
|
+
state.open = false;
|
|
144
|
+
state.user = data.user;
|
|
145
|
+
state.provider = data.provider;
|
|
146
|
+
state.walletOS = data.walletOS;
|
|
147
|
+
}
|
|
148
|
+
if (!getSessionToken()) {
|
|
149
|
+
// eslint-disable-next-line quotes
|
|
150
|
+
Toast.error(`Can't write session token, please use https url to access this page`);
|
|
151
|
+
// NOTICE: 出现这种情况暂不移除 token,可以通过刷新页面看是否能解决
|
|
152
|
+
// removeToken();
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
// We may have an invalid token
|
|
156
|
+
if (showProgress) {
|
|
157
|
+
state.loading = pageState.autoConnect;
|
|
158
|
+
}
|
|
159
|
+
state.open = pageState.autoConnect;
|
|
160
|
+
state.user = null;
|
|
161
|
+
state.provider = '';
|
|
162
|
+
state.walletOS = '';
|
|
163
|
+
state.unReadCount = 0;
|
|
164
|
+
throw new ErrorToken('Invalid token');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { data, status };
|
|
168
|
+
} catch (err) {
|
|
169
|
+
logger.error('SessionProvider.refresh error', err);
|
|
170
|
+
state.open = false;
|
|
171
|
+
state.error = err.message;
|
|
172
|
+
if (err?.response?.status === 400) {
|
|
173
|
+
throw new ErrorToken('Invalid token');
|
|
174
|
+
} else {
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
if (showProgress) {
|
|
179
|
+
state.loading = false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// eslint-disable-next-line require-await
|
|
186
|
+
const handleRefreshToken = useMemoizedFn(async (showProgress = false, onlyRefreshToken = false) => {
|
|
187
|
+
debug('handleRefreshToken', { showProgress, onlyRefreshToken });
|
|
188
|
+
const requestFn = () =>
|
|
189
|
+
request.post(withQuery(joinURL(pageState.prefix, '/refreshSession')), null, {
|
|
190
|
+
headers: {
|
|
191
|
+
authorization: `Bearer ${encodeURIComponent(getRefreshToken())}`,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
setIntervalTime(undefined);
|
|
196
|
+
// 此时 sessionToken 已经没用了,直接移除
|
|
197
|
+
// HACK: 不使用手动删除 sessionToken 的行为,以免造成应用中短时出现没有 sessionToken 的情况 2023-07-18
|
|
198
|
+
// removeSessionToken();
|
|
199
|
+
try {
|
|
200
|
+
await _refresh({ showProgress, requestFn, onlyRefreshToken, type: 'refreshToken' });
|
|
201
|
+
setRetryCount(0);
|
|
202
|
+
setIntervalTime(CHECK_INTERVAL_TIME);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (err instanceof ErrorToken) {
|
|
205
|
+
console.warn('refresh token failed, remove all tokens');
|
|
206
|
+
setRetryCount(0);
|
|
207
|
+
removeToken();
|
|
208
|
+
setIntervalTime(CHECK_INTERVAL_TIME);
|
|
209
|
+
} else {
|
|
210
|
+
// HACK: 此时不能 removeToken,因为可能是网络问题导致的,而不是 token 问题
|
|
211
|
+
logger.error('refresh token failed, unexpected error:', err);
|
|
212
|
+
setRetryCount(retryCount + 1);
|
|
213
|
+
await sleep(CHECK_INTERVAL_TIME);
|
|
214
|
+
if (retryCount >= MAX_RETRY_COUNT) {
|
|
215
|
+
// @fix https://github.com/ArcBlock/blocklet-server/issues/9754
|
|
216
|
+
// @fix https://github.com/ArcBlock/blocklet-server/issues/9422
|
|
217
|
+
setRetryCount(0);
|
|
218
|
+
setIntervalTime(undefined);
|
|
219
|
+
} else {
|
|
220
|
+
setIntervalTime(CHECK_INTERVAL_TIME);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
const checkToken = useMemoizedFn((force = false) => {
|
|
226
|
+
if (lazyRefreshToken) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const sessionToken = getSessionToken();
|
|
230
|
+
let needRefresh = force;
|
|
231
|
+
const now = new Date().getTime();
|
|
232
|
+
if (sessionToken) {
|
|
233
|
+
try {
|
|
234
|
+
const { exp, iat } = jwtDecode(sessionToken);
|
|
235
|
+
// NOTICE: 当 sessionToken 剩余有效期为设定有效期的一半时,自动更新一次 token
|
|
236
|
+
if (exp * 1000 - now < now - iat * 1000) {
|
|
237
|
+
needRefresh = true;
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
needRefresh = true;
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
needRefresh = true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (needRefresh) {
|
|
247
|
+
const refreshToken = getRefreshToken();
|
|
248
|
+
// 如果 sessionToken 不存在但是 refreshToken 存在, 需要自动刷新
|
|
249
|
+
if (refreshToken) {
|
|
250
|
+
handleRefreshToken(false, true);
|
|
251
|
+
} else {
|
|
252
|
+
clearSession();
|
|
253
|
+
// HACK: 在使用 useReactive 后,即使重复的给子字段赋值为 null,也会引起组件的重渲染,所以对于定时循环来说,一定要避免这一点,防止无效的赋值引起页面重渲染
|
|
254
|
+
if (state.user) {
|
|
255
|
+
state.user = null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// ek 参数用于获取 nextToken, 参考: https://github.com/ArcBlock/blocklet-server/issues/5944
|
|
262
|
+
const handleRefreshUser = useMemoizedFn(async ({ showProgress = false, forceRefreshToken = false } = {}) => {
|
|
263
|
+
const requestFn = () =>
|
|
264
|
+
pRetry(() => service.get(joinURL(pageState.prefix, '/session'), { secure: true }), {
|
|
265
|
+
retries: 2,
|
|
266
|
+
});
|
|
267
|
+
debug('handleRefreshUser', { showProgress, forceRefreshToken });
|
|
268
|
+
try {
|
|
269
|
+
if (forceRefreshToken) {
|
|
270
|
+
// 相当于强制触发一次 refreshToken 的刷新
|
|
271
|
+
throw new ErrorToken('need force refresh token');
|
|
272
|
+
}
|
|
273
|
+
await _refresh({ showProgress, requestFn, type: 'refreshUser' });
|
|
274
|
+
} catch (err) {
|
|
275
|
+
if (err instanceof ErrorToken) {
|
|
276
|
+
debug('handleRefreshUser failed, try to refresh token', { err });
|
|
277
|
+
await handleRefreshToken();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const syncSessionSate = useMemoizedFn(async () => {
|
|
283
|
+
const sessionToken = getSessionToken();
|
|
284
|
+
const refreshToken = getRefreshToken();
|
|
285
|
+
if (state.user) return;
|
|
286
|
+
|
|
287
|
+
if (sessionToken || refreshToken) {
|
|
288
|
+
await handleRefreshUser();
|
|
289
|
+
} else {
|
|
290
|
+
clearSession();
|
|
291
|
+
if (state.user) {
|
|
292
|
+
state.user = null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const renewToken = useMemoizedFn(() => {
|
|
298
|
+
if (!state.initialized) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 没有 session 时,页面获得焦点不应该去刷新 session
|
|
303
|
+
if (!state.user) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
checkToken();
|
|
309
|
+
} catch {
|
|
310
|
+
// do nothing
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const handleLoginResult = useMemoizedFn((result) => {
|
|
315
|
+
const loginResult = Array.isArray(result) ? result[0] : result;
|
|
316
|
+
debug('handleLoginResult', { loginResult, result });
|
|
317
|
+
const { loginToken, sessionToken, refreshToken, visitorId, encrypted = true } = loginResult;
|
|
318
|
+
const token = loginToken || sessionToken;
|
|
319
|
+
let decryptSessionToken;
|
|
320
|
+
let decryptRefreshToken;
|
|
321
|
+
let decryptVisitorId;
|
|
322
|
+
if (token) {
|
|
323
|
+
decryptSessionToken = encrypted ? decrypt(token) : token;
|
|
324
|
+
debug('handleLoginResult: setSessionToken', { decryptSessionToken });
|
|
325
|
+
setSessionToken(decryptSessionToken);
|
|
326
|
+
|
|
327
|
+
if (refreshToken) {
|
|
328
|
+
decryptRefreshToken = encrypted ? decrypt(refreshToken) : refreshToken;
|
|
329
|
+
debug('handleLoginResult: setRefreshToken', { decryptRefreshToken });
|
|
330
|
+
setRefreshToken(decryptRefreshToken);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (visitorId) {
|
|
334
|
+
decryptVisitorId = encrypted ? decrypt(visitorId) : visitorId;
|
|
335
|
+
debug('handleLoginResult: setVisitorId', { decryptVisitorId });
|
|
336
|
+
setVisitorId(decryptVisitorId);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
useInterval(
|
|
341
|
+
() => {
|
|
342
|
+
checkCookieLocale();
|
|
343
|
+
checkToken();
|
|
344
|
+
},
|
|
345
|
+
intervalTime,
|
|
346
|
+
{ immediate: true }
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
renewToken,
|
|
351
|
+
handleRefreshUser,
|
|
352
|
+
handleRefreshToken,
|
|
353
|
+
syncSessionSate,
|
|
354
|
+
handleLoginResult,
|
|
355
|
+
decrypt,
|
|
356
|
+
removeToken,
|
|
357
|
+
clearSession,
|
|
358
|
+
getSessionToken,
|
|
359
|
+
getRefreshToken,
|
|
360
|
+
setRefreshToken,
|
|
361
|
+
setSessionToken,
|
|
362
|
+
|
|
363
|
+
service,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 函数二次验证 hook
|
|
3
|
+
*/
|
|
4
|
+
import { translate } from '@arcblock/ux/lib/Locale/util';
|
|
5
|
+
import { LOGIN_PROVIDER } from '@blocklet/constant';
|
|
6
|
+
import { toBase64 } from '@ocap/util';
|
|
7
|
+
import { translations } from '../libs/locales';
|
|
8
|
+
|
|
9
|
+
export default function useVerify({ state, locale, connectApi }) {
|
|
10
|
+
const { user } = state;
|
|
11
|
+
|
|
12
|
+
// 是否要使用 passkey 进行验证
|
|
13
|
+
const connectedAccounts = user?.connectedAccounts || [];
|
|
14
|
+
const hasPasskey = connectedAccounts.some((x) => x.provider === LOGIN_PROVIDER.PASSKEY);
|
|
15
|
+
const hasEmail = connectedAccounts.some((x) => x.provider === LOGIN_PROVIDER.EMAIL);
|
|
16
|
+
const hasGithub = connectedAccounts.some((x) => x.provider === LOGIN_PROVIDER.GITHUB);
|
|
17
|
+
const hasGoogle = connectedAccounts.some((x) => x.provider === LOGIN_PROVIDER.GOOGLE);
|
|
18
|
+
const hasApple = connectedAccounts.some((x) => x.provider === LOGIN_PROVIDER.APPLE);
|
|
19
|
+
const passkeyBehavior = hasPasskey ? 'only-existing' : 'none';
|
|
20
|
+
const enabledConnectTypes = ['web', 'mobile'];
|
|
21
|
+
|
|
22
|
+
if (hasEmail) {
|
|
23
|
+
enabledConnectTypes.push('email');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (hasGithub) {
|
|
27
|
+
enabledConnectTypes.push('github');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (hasGoogle) {
|
|
31
|
+
enabledConnectTypes.push('google');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (hasApple) {
|
|
35
|
+
enabledConnectTypes.push('apple');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const t = (key, data = {}) => {
|
|
39
|
+
return translate(translations.verify, key, locale, 'en', data);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (options = {}) => {
|
|
43
|
+
const { payload, ...rest } = options.extraParams || {};
|
|
44
|
+
const extraParams = {
|
|
45
|
+
payload: toBase64(JSON.stringify({ action: options.operation || 'destroy-self', input: rest.input || {} })),
|
|
46
|
+
...(rest || {}),
|
|
47
|
+
};
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
connectApi.open({
|
|
50
|
+
locale,
|
|
51
|
+
action: options.action || 'destroy-self', // 每次都需要验证的
|
|
52
|
+
forceConnected: true,
|
|
53
|
+
saveConnect: false,
|
|
54
|
+
autoConnect: false,
|
|
55
|
+
className: 'connect',
|
|
56
|
+
checkTimeout: 10 * 60 * 1000,
|
|
57
|
+
passkeyBehavior,
|
|
58
|
+
enabledConnectTypes,
|
|
59
|
+
messages: options.messages || {
|
|
60
|
+
title: t('title'),
|
|
61
|
+
scan: t('scan'),
|
|
62
|
+
confirm: t('confirm'),
|
|
63
|
+
success: t('success'),
|
|
64
|
+
},
|
|
65
|
+
extraParams,
|
|
66
|
+
onSuccess: (result, decrypt = (x) => x) => {
|
|
67
|
+
resolve({ result, input: rest.input, sessionId: decrypt(result.destroySessionId) });
|
|
68
|
+
},
|
|
69
|
+
onClose: () => {
|
|
70
|
+
reject(new Error(t('abort')));
|
|
71
|
+
connectApi.close();
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
}
|