@arcblock/did-connect-react 3.4.14 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/dist/standalone/did-connect-react.css +1 -0
  2. package/dist/standalone/index.js +133700 -0
  3. package/lib/Connect/components/login-item/connect-choose-list.js +111 -111
  4. package/lib/Connect/components/login-item/connect-provider-list.js +180 -180
  5. package/lib/Connect/components/login-item/mobile-login-item.js +56 -56
  6. package/lib/Connect/components/login-item/passkey-login-item.js +27 -29
  7. package/lib/Connect/components/login-item/web-login-item.js +31 -29
  8. package/lib/Connect/connect.js +202 -197
  9. package/lib/Connect/contexts/state.js +19 -17
  10. package/lib/Connect/hooks/provider-list.js +33 -33
  11. package/lib/Connect/plugins/email/list-item.js +14 -14
  12. package/lib/Connect/plugins/email/placeholder.js +77 -76
  13. package/lib/package.json.js +1 -1
  14. package/package.json +14 -9
  15. package/.aigne/doc-smith/config.yaml +0 -85
  16. package/.aigne/doc-smith/history.yaml +0 -6
  17. package/.aigne/doc-smith/output/structure-plan.json +0 -204
  18. package/.aigne/doc-smith/translation-cache.yaml +0 -11
  19. package/.aigne/doc-smith/upload-cache.yaml +0 -213
  20. package/docs/_sidebar.md +0 -18
  21. package/docs/advanced-authentication-methods.ja.md +0 -261
  22. package/docs/advanced-authentication-methods.md +0 -261
  23. package/docs/advanced-authentication-methods.zh-TW.md +0 -261
  24. package/docs/advanced-authentication-methods.zh.md +0 -261
  25. package/docs/advanced-utilities.ja.md +0 -132
  26. package/docs/advanced-utilities.md +0 -132
  27. package/docs/advanced-utilities.zh-TW.md +0 -132
  28. package/docs/advanced-utilities.zh.md +0 -132
  29. package/docs/advanced.ja.md +0 -95
  30. package/docs/advanced.md +0 -95
  31. package/docs/advanced.zh-TW.md +0 -95
  32. package/docs/advanced.zh.md +0 -95
  33. package/docs/api-reference.ja.md +0 -178
  34. package/docs/api-reference.md +0 -178
  35. package/docs/api-reference.zh-TW.md +0 -178
  36. package/docs/api-reference.zh.md +0 -178
  37. package/docs/assets/diagram/core-components-session-provider-01.ja.jpg +0 -0
  38. package/docs/assets/diagram/core-components-session-provider-01.jpg +0 -0
  39. package/docs/assets/diagram/core-components-session-provider-01.zh-TW.jpg +0 -0
  40. package/docs/assets/diagram/core-components-session-provider-01.zh.jpg +0 -0
  41. package/docs/assets/diagram/did-connect-diagram-0.ja.jpg +0 -0
  42. package/docs/assets/diagram/did-connect-diagram-0.jpg +0 -0
  43. package/docs/assets/diagram/did-connect-diagram-0.zh-TW.jpg +0 -0
  44. package/docs/assets/diagram/did-connect-diagram-0.zh.jpg +0 -0
  45. package/docs/assets/diagram/overview-01.ja.jpg +0 -0
  46. package/docs/assets/diagram/overview-01.jpg +0 -0
  47. package/docs/assets/diagram/overview-01.zh-TW.jpg +0 -0
  48. package/docs/assets/diagram/overview-01.zh.jpg +0 -0
  49. package/docs/assets/diagram/use-connect-diagram-0.ja.jpg +0 -0
  50. package/docs/assets/diagram/use-connect-diagram-0.jpg +0 -0
  51. package/docs/assets/diagram/use-connect-diagram-0.zh-TW.jpg +0 -0
  52. package/docs/assets/diagram/use-connect-diagram-0.zh.jpg +0 -0
  53. package/docs/core-components-did-connect.ja.md +0 -166
  54. package/docs/core-components-did-connect.md +0 -166
  55. package/docs/core-components-did-connect.zh-TW.md +0 -166
  56. package/docs/core-components-did-connect.zh.md +0 -166
  57. package/docs/core-components-session-provider.ja.md +0 -197
  58. package/docs/core-components-session-provider.md +0 -197
  59. package/docs/core-components-session-provider.zh-TW.md +0 -197
  60. package/docs/core-components-session-provider.zh.md +0 -197
  61. package/docs/core-components.ja.md +0 -16
  62. package/docs/core-components.md +0 -16
  63. package/docs/core-components.zh-TW.md +0 -16
  64. package/docs/core-components.zh.md +0 -16
  65. package/docs/getting-started.ja.md +0 -138
  66. package/docs/getting-started.md +0 -138
  67. package/docs/getting-started.zh-TW.md +0 -138
  68. package/docs/getting-started.zh.md +0 -138
  69. package/docs/hooks-use-connect.ja.md +0 -178
  70. package/docs/hooks-use-connect.md +0 -178
  71. package/docs/hooks-use-connect.zh-TW.md +0 -178
  72. package/docs/hooks-use-connect.zh.md +0 -178
  73. package/docs/hooks-use-did.ja.md +0 -107
  74. package/docs/hooks-use-did.md +0 -107
  75. package/docs/hooks-use-did.zh-TW.md +0 -107
  76. package/docs/hooks-use-did.zh.md +0 -107
  77. package/docs/hooks-use-oauth-passkey.ja.md +0 -188
  78. package/docs/hooks-use-oauth-passkey.md +0 -188
  79. package/docs/hooks-use-oauth-passkey.zh-TW.md +0 -188
  80. package/docs/hooks-use-oauth-passkey.zh.md +0 -188
  81. package/docs/hooks.ja.md +0 -23
  82. package/docs/hooks.md +0 -23
  83. package/docs/hooks.zh-TW.md +0 -23
  84. package/docs/hooks.zh.md +0 -23
  85. package/docs/overview.ja.md +0 -119
  86. package/docs/overview.md +0 -119
  87. package/docs/overview.zh-TW.md +0 -119
  88. package/docs/overview.zh.md +0 -119
  89. package/docs/ui-components-address.ja.md +0 -121
  90. package/docs/ui-components-address.md +0 -121
  91. package/docs/ui-components-address.zh-TW.md +0 -121
  92. package/docs/ui-components-address.zh.md +0 -121
  93. package/docs/ui-components-avatar.ja.md +0 -65
  94. package/docs/ui-components-avatar.md +0 -65
  95. package/docs/ui-components-avatar.zh-TW.md +0 -65
  96. package/docs/ui-components-avatar.zh.md +0 -65
  97. package/docs/ui-components-button.ja.md +0 -99
  98. package/docs/ui-components-button.md +0 -99
  99. package/docs/ui-components-button.zh-TW.md +0 -99
  100. package/docs/ui-components-button.zh.md +0 -99
  101. package/docs/ui-components-logo.ja.md +0 -52
  102. package/docs/ui-components-logo.md +0 -52
  103. package/docs/ui-components-logo.zh-TW.md +0 -52
  104. package/docs/ui-components-logo.zh.md +0 -52
  105. package/docs/ui-components.ja.md +0 -57
  106. package/docs/ui-components.md +0 -57
  107. package/docs/ui-components.zh-TW.md +0 -57
  108. package/docs/ui-components.zh.md +0 -57
  109. package/glossary.md +0 -1
  110. package/src/Address/index.jsx +0 -2
  111. package/src/Avatar/index.jsx +0 -2
  112. package/src/Button/Button.stories.jsx +0 -7
  113. package/src/Button/index.jsx +0 -21
  114. package/src/Connect/Connect.stories.jsx +0 -34
  115. package/src/Connect/assets/locale.js +0 -149
  116. package/src/Connect/assets/login-bg.png +0 -0
  117. package/src/Connect/assets/login-slogan.js +0 -7
  118. package/src/Connect/components/action-button.jsx +0 -22
  119. package/src/Connect/components/app-tips.jsx +0 -156
  120. package/src/Connect/components/auto-height.jsx +0 -38
  121. package/src/Connect/components/back-button.jsx +0 -24
  122. package/src/Connect/components/connect-status.jsx +0 -259
  123. package/src/Connect/components/did-connect-title.jsx +0 -107
  124. package/src/Connect/components/download-tips.jsx +0 -55
  125. package/src/Connect/components/loading.jsx +0 -25
  126. package/src/Connect/components/login-item/connect-choose-list.jsx +0 -317
  127. package/src/Connect/components/login-item/connect-provider-list.jsx +0 -462
  128. package/src/Connect/components/login-item/login-method-item.jsx +0 -139
  129. package/src/Connect/components/login-item/mobile-login-item.jsx +0 -181
  130. package/src/Connect/components/login-item/passkey-login-item.jsx +0 -54
  131. package/src/Connect/components/login-item/wallet-login-options.jsx +0 -129
  132. package/src/Connect/components/login-item/web-login-item.jsx +0 -157
  133. package/src/Connect/components/mask-overlay.jsx +0 -32
  134. package/src/Connect/components/refresh-overlay.jsx +0 -52
  135. package/src/Connect/components/switch-app.jsx +0 -69
  136. package/src/Connect/connect.jsx +0 -617
  137. package/src/Connect/contexts/state.jsx +0 -234
  138. package/src/Connect/fallback-connect.jsx +0 -47
  139. package/src/Connect/fullpage.jsx +0 -3
  140. package/src/Connect/hooks/auth-url.js +0 -31
  141. package/src/Connect/hooks/method-list.js +0 -121
  142. package/src/Connect/hooks/page-show.js +0 -24
  143. package/src/Connect/hooks/provider-list.js +0 -165
  144. package/src/Connect/hooks/security.js +0 -40
  145. package/src/Connect/hooks/token.js +0 -627
  146. package/src/Connect/hooks/use-apps.js +0 -69
  147. package/src/Connect/hooks/use-quick-connect.js +0 -119
  148. package/src/Connect/index.jsx +0 -21
  149. package/src/Connect/landing-page.jsx +0 -3
  150. package/src/Connect/plugins/email/index.jsx +0 -85
  151. package/src/Connect/plugins/email/list-item.jsx +0 -34
  152. package/src/Connect/plugins/email/placeholder.jsx +0 -365
  153. package/src/Connect/plugins/index.js +0 -2
  154. package/src/Connect/use-connect.jsx +0 -321
  155. package/src/Connect/with-blocklet.jsx +0 -26
  156. package/src/Connect/with-bridge-call.jsx +0 -138
  157. package/src/Federated/context.jsx +0 -93
  158. package/src/Federated/index.jsx +0 -1
  159. package/src/Logo/index.jsx +0 -2
  160. package/src/OAuth/bind-conflict-alert.jsx +0 -37
  161. package/src/OAuth/context.jsx +0 -407
  162. package/src/OAuth/guest.svg +0 -20
  163. package/src/OAuth/index.jsx +0 -1
  164. package/src/OAuth/passport-switcher.jsx +0 -2
  165. package/src/Passkey/actions.jsx +0 -217
  166. package/src/Passkey/constants.js +0 -2
  167. package/src/Passkey/context.jsx +0 -395
  168. package/src/Passkey/dialog.jsx +0 -401
  169. package/src/Passkey/icon.jsx +0 -10
  170. package/src/Passkey/index.jsx +0 -2
  171. package/src/Service/index.jsx +0 -96
  172. package/src/Session/assets/did-spaces-guide-cover.svg +0 -1
  173. package/src/Session/assets/did-spaces-guide-icon.svg +0 -7
  174. package/src/Session/context.jsx +0 -7
  175. package/src/Session/did-spaces-guide.jsx +0 -173
  176. package/src/Session/handler.jsx +0 -98
  177. package/src/Session/hooks/use-federated.js +0 -91
  178. package/src/Session/hooks/use-mobile.jsx +0 -6
  179. package/src/Session/hooks/use-protected-routes.js +0 -16
  180. package/src/Session/hooks/use-session-token.js +0 -400
  181. package/src/Session/hooks/use-verify.jsx +0 -76
  182. package/src/Session/index.jsx +0 -1789
  183. package/src/Session/libs/constants.js +0 -17
  184. package/src/Session/libs/did-spaces.js +0 -38
  185. package/src/Session/libs/federated.js +0 -82
  186. package/src/Session/libs/index.js +0 -5
  187. package/src/Session/libs/locales.js +0 -160
  188. package/src/Session/libs/login-mobile.js +0 -80
  189. package/src/Session/window-focus-aware.jsx +0 -28
  190. package/src/SessionManager/index.jsx +0 -2
  191. package/src/Storage/engine/cookie.js +0 -25
  192. package/src/Storage/engine/local-storage.js +0 -57
  193. package/src/Storage/index.js +0 -25
  194. package/src/User/index.js +0 -4
  195. package/src/User/use-did.js +0 -80
  196. package/src/User/wrap-did.jsx +0 -18
  197. package/src/WebWalletSWKeeper/index.jsx +0 -3
  198. package/src/components/PassportSwitcher.jsx +0 -160
  199. package/src/constant.js +0 -27
  200. package/src/error.js +0 -6
  201. package/src/hooks/use-locale.jsx +0 -6
  202. package/src/index.js +0 -32
  203. package/src/locales/en.jsx +0 -15
  204. package/src/locales/index.jsx +0 -13
  205. package/src/locales/zh.jsx +0 -15
  206. package/src/types.d.ts +0 -355
  207. package/src/utils.js +0 -413
  208. package/vite.config.mjs +0 -29
@@ -1,1789 +0,0 @@
1
- /* eslint-disable no-use-before-define */
2
- import PropTypes from 'prop-types';
3
- import omit from 'lodash/omit';
4
- import pick from 'lodash/pick';
5
- import cloneDeep from 'lodash/cloneDeep';
6
- import isFunction from 'lodash/isFunction';
7
- import isUndefined from 'lodash/isUndefined';
8
- import defaultsDeep from 'lodash/defaultsDeep';
9
- import Cookie from 'js-cookie';
10
- import EventEmitter from 'eventemitter3';
11
- import pWaitFor from 'p-wait-for';
12
- import { Box, CircularProgress, Typography, useTheme } from '@mui/material';
13
- import { getVisitorId, setVisitorId, ensureVisitorId, isUrl, mergeIgnoreEmpty } from '@arcblock/ux/lib/Util';
14
- import Toast, { ToastProvider } from '@arcblock/ux/lib/Toast';
15
- import Center from '@arcblock/ux/lib/Center';
16
- import { useCreation, useLatest, useMemoizedFn, useMount, usePrevious, useReactive, useUpdateEffect } from 'ahooks';
17
- import { joinURL } from 'ufo';
18
- import { Icon } from '@iconify/react';
19
- import KeyboardDoubleArrowRightRoundedIcon from '@iconify-icons/material-symbols/keyboard-double-arrow-right-rounded';
20
- import useBrowser from '@arcblock/react-hooks/lib/useBrowser';
21
- import useConfirm from '@arcblock/ux/lib/Dialog/use-confirm';
22
- import DID from '@arcblock/ux/lib/DID';
23
- import Avatar from '@arcblock/ux/lib/Avatar';
24
- import { getCurrentAppPid } from '@arcblock/ux/lib/SessionUser/libs/utils';
25
- import { translate } from '@arcblock/ux/lib/Locale/util';
26
- import { getBlockletSDK } from '@blocklet/js-sdk';
27
- import bridge from '@arcblock/bridge';
28
- import { getFederatedEnabled } from '@arcblock/ux/lib/Util/federated';
29
- import noop from 'lodash/noop';
30
- import isNil from 'lodash/isNil';
31
- import { ReactGA } from '@arcblock/ux/lib/withTracker';
32
-
33
- import { useEffect, useState } from 'react';
34
- import createStorage from '../Storage';
35
- import {
36
- getAppId,
37
- updateConnectedInfo,
38
- getBrowserLang,
39
- formatCacheTtl,
40
- sleep,
41
- decodeUrlParams,
42
- logger,
43
- debug,
44
- debugTmp,
45
- } from '../utils';
46
- import WindowFocusAware from './window-focus-aware';
47
- import { OAuthProvider, useOAuth, OAuthConsumer, OAuthContext } from '../OAuth';
48
- import { PasskeyProvider, usePasskey, PasskeyConsumer, PasskeyContext } from '../Passkey';
49
- import { useDid, WrapDid } from '../User';
50
- import useFederated from './hooks/use-federated';
51
- import useSessionToken from './hooks/use-session-token';
52
- import useProtectedRoutes from './hooks/use-protected-routes';
53
- import { SessionContext } from './context';
54
- import useConnect from '../Connect/use-connect';
55
- import { EVENTS } from './libs/constants';
56
- import { translations } from './libs/locales';
57
- import { NotOpenError } from '../error';
58
- import {
59
- API_DID_PREFIX,
60
- BLOCKLET_SERVICE_PATH_PREFIX,
61
- DEFAULT_TIMEOUT,
62
- REFRESH_TOKEN_STORAGE_KEY,
63
- SESSION_TOKEN_STORAGE_KEY,
64
- DID_SPACES_BASE_URL,
65
- CSRF_TOKEN_STORAGE_KEY,
66
- } from '../constant';
67
- import { checkEnableAutoLogin, getMobileVisitorId, login as loginInMobile } from './libs/login-mobile';
68
- import useQuickConnect from '../Connect/hooks/use-quick-connect';
69
- import { didSpacesIsRequired } from './libs/did-spaces';
70
- import { getWalletDid } from '../User/use-did';
71
- import useDIDSpacesGuide from './did-spaces-guide';
72
- import { FederatedProvider, useFederatedContext } from '../Federated';
73
- import useVerify from './hooks/use-verify';
74
- import {
75
- gaBindWalletFailedHandler,
76
- gaBindWalletSuccessHandler,
77
- gaLoginFailedHandler,
78
- gaLoginSuccessHandler,
79
- gaSwitchPassportFailedHandler,
80
- gaSwitchPassportSuccessHandler,
81
- } from './handler';
82
-
83
- export * from './libs';
84
-
85
- const { Provider, Consumer } = SessionContext;
86
-
87
- const didConnectTimeoutError = {
88
- en: 'DID Connect timeout, please reopen DID Connect popup',
89
- zh: 'DID Connect 超时, 请重新打开 DID Connect',
90
- };
91
-
92
- const waitForDidConnect = {
93
- en: 'DID Connect is already opened, please wait for it to complete',
94
- zh: 'DID Connect 已打开,请等待完成',
95
- };
96
-
97
- function createSessionContext(
98
- storageKey = SESSION_TOKEN_STORAGE_KEY,
99
- storageEngine = 'ls',
100
- storageOptions = {},
101
- opts = {}
102
- ) {
103
- let defaultServiceHost = '/';
104
- // FIXME: @zhanghan 当 connect 组件使用不同的 baseUrl 时,不应该直接从 window.blocklet 去取值
105
- if (globalThis?.blocklet?.prefix) {
106
- defaultServiceHost = globalThis.blocklet.prefix;
107
- }
108
- if (typeof opts === 'boolean') {
109
- // eslint-disable-next-line no-param-reassign
110
- opts = {
111
- appendAuthServicePrefix: opts,
112
- extraParams: {},
113
- };
114
- }
115
-
116
- defaultsDeep(opts, {
117
- rolling: true,
118
- appendAuthServicePrefix: false,
119
- extraParams: {},
120
- refreshTokenStorageKey: REFRESH_TOKEN_STORAGE_KEY,
121
- });
122
-
123
- function notifyBridge(sessionState) {
124
- if (globalThis.blocklet) {
125
- if (sessionState?.user) {
126
- const data = {
127
- did: sessionState.user.did,
128
- host: window.location.host,
129
- appPid: globalThis.blocklet.appPid,
130
- visitorId: getVisitorId(),
131
- sourceAppPid: sessionState.user.sourceAppPid,
132
- fullName: sessionState.user.fullName,
133
- };
134
- debug('bridge callArc: onLogin', data);
135
- bridge.callArc('onLogin', { ...data, user: data });
136
- } else {
137
- bridge.callArc('onLogin', { error: 'no user', code: 400 });
138
- debug('notifyBridge failed', { sessionState });
139
- }
140
- }
141
- }
142
-
143
- const sessionTokenStorage = createStorage(storageKey, storageEngine, storageOptions);
144
- const csrfTokenStorage = createStorage(CSRF_TOKEN_STORAGE_KEY, 'cookie', {
145
- secure: true,
146
- sameSite: 'strict',
147
- });
148
-
149
- const refreshTokenStorage = createStorage(opts.refreshTokenStorageKey, 'ls');
150
-
151
- function SessionProvider({ ...rawProps }) {
152
- const sdk = getBlockletSDK();
153
- const props = Object.assign({}, rawProps);
154
- if (isUndefined(props.serviceHost)) {
155
- props.serviceHost = defaultServiceHost;
156
- }
157
- if (isUndefined(props.locale)) {
158
- props.locale = '';
159
- }
160
- if (isUndefined(props.action)) {
161
- props.action = 'login';
162
- }
163
- if (isUndefined(props.prefix)) {
164
- props.prefix = API_DID_PREFIX;
165
- }
166
- if (isUndefined(props.appendAuthServicePrefix)) {
167
- props.appendAuthServicePrefix = false;
168
- }
169
- if (isUndefined(props.extraParams)) {
170
- props.extraParams = {};
171
- }
172
- if (isUndefined(props.options)) {
173
- props.options = {};
174
- }
175
- if (isUndefined(props.autoConnect)) {
176
- props.autoConnect = false;
177
- }
178
- if (isUndefined(props.autoDisconnect)) {
179
- props.autoDisconnect = true;
180
- }
181
- if (isUndefined(props.useSocket)) {
182
- props.useSocket = true;
183
- }
184
- if (isUndefined(props.timeout)) {
185
- props.timeout = DEFAULT_TIMEOUT * 1000;
186
- }
187
- if (isUndefined(props.webWalletUrl)) {
188
- props.webWalletUrl = undefined;
189
- }
190
- if (isUndefined(props.protectedRoutes)) {
191
- props.protectedRoutes = ['*'];
192
- }
193
- if (isUndefined(props.apiOptions)) {
194
- props.apiOptions = {};
195
- }
196
- if (isUndefined(props.lazyRefreshToken)) {
197
- props.lazyRefreshToken = false;
198
- }
199
-
200
- const { connectApi, connectHolder } = useConnect();
201
- const [unReadCount, setUnReadCount] = useState(0);
202
- const browser = useBrowser();
203
- const { requestStorageAccess } = useFederatedContext();
204
- const { palette } = useTheme();
205
- const searchParams = new URLSearchParams(window.location.search);
206
-
207
- /**
208
- * @typedef PageState
209
- * @property {object} extraParams - 额外参数
210
- * @property {object} options - 配置项
211
- * @property {string} currentLocale - 当前语言
212
- * @property {boolean} allowWallet - 是否显示钱包操作入口
213
- * @property {('popup'|'window'|'iframe')} [openMode] - 打开的方式
214
- * @property {boolean} autoConnect - 是否自动连接
215
- * @property {string} prefix - 自动生成的当前应用的 prefix
216
- */
217
- /**
218
- * 页面状态
219
- * @type {PageState}
220
- */
221
- const pageState = useReactive({
222
- extraParams: {},
223
- options: {},
224
- currentLocale: props.locale,
225
- allowWallet: undefined,
226
- openMode: 'window',
227
- get autoConnect() {
228
- // for backward compatibility
229
- if (typeof props.autoConnect === 'boolean') {
230
- return props.autoConnect;
231
- }
232
- // eslint-disable-next-line react/prop-types
233
- return !!props.autoLogin;
234
- },
235
- get prefix() {
236
- if (opts.appendAuthServicePrefix || props.appendAuthServicePrefix) {
237
- return joinURL(BLOCKLET_SERVICE_PATH_PREFIX, props.prefix);
238
- }
239
- return props.prefix;
240
- },
241
- get notificationPrefix() {
242
- const _prefix = '/api/notifications';
243
- if (opts.appendAuthServicePrefix || props.appendAuthServicePrefix) {
244
- return joinURL(BLOCKLET_SERVICE_PATH_PREFIX, _prefix);
245
- }
246
- return _prefix;
247
- },
248
- });
249
-
250
- const state = useReactive({
251
- action: props.action,
252
- error: '',
253
- initialized: false,
254
- loading: false,
255
- open: false,
256
- user: null,
257
- provider: '',
258
- walletOS: '',
259
- baseUrl: '',
260
- unReadCount: 0,
261
- // 不可以直接个性 props.autoConnect (readonly)
262
- });
263
- const currentLocale = useCreation(() => {
264
- return props.locale || pageState.currentLocale || getBrowserLang();
265
- }, [props.locale, pageState.currentLocale]);
266
-
267
- const { confirmApi, confirmHolder } = useConfirm();
268
-
269
- const stopOnOpen = useMemoizedFn(() => {
270
- if (state.open && !browser.arcSphere) {
271
- const msg = waitForDidConnect[currentLocale] || waitForDidConnect.en;
272
- Toast.warning(msg);
273
- throw new Error(msg);
274
- }
275
- });
276
-
277
- const events = useCreation(() => new EventEmitter(), []);
278
- // Register event listeners with cleanup to prevent memory leaks
279
- useEffect(() => {
280
- events.on(EVENTS.LOGIN, gaLoginSuccessHandler);
281
- events.on(EVENTS.LOGIN_FAILED, gaLoginFailedHandler);
282
- events.on(EVENTS.SWITCH_PASSPORT, gaSwitchPassportSuccessHandler);
283
- events.on(EVENTS.SWITCH_PASSPORT_FAILED, gaSwitchPassportFailedHandler);
284
- events.on(EVENTS.BIND_WALLET, gaBindWalletSuccessHandler);
285
- events.on(EVENTS.BIND_WALLET_FAILED, gaBindWalletFailedHandler);
286
- return () => {
287
- events.off(EVENTS.LOGIN, gaLoginSuccessHandler);
288
- events.off(EVENTS.LOGIN_FAILED, gaLoginFailedHandler);
289
- events.off(EVENTS.SWITCH_PASSPORT, gaSwitchPassportSuccessHandler);
290
- events.off(EVENTS.SWITCH_PASSPORT_FAILED, gaSwitchPassportFailedHandler);
291
- events.off(EVENTS.BIND_WALLET, gaBindWalletSuccessHandler);
292
- events.off(EVENTS.BIND_WALLET_FAILED, gaBindWalletFailedHandler);
293
- };
294
- }, [events]);
295
-
296
- /**
297
- * 用于包装 did-connect 的 open 和 close 事件,添加一些默认的行为
298
- * 1. 在 open 中添加的事件订阅,did-connect close 时需要取消订阅
299
- * 2. session 内部的事件订阅使用与外部事件不同命的名字
300
- * @param {string} eventName - 订阅的事件名
301
- * @param {function} doneFn - 订阅事件被调用时,执行的函数
302
- * @param {function} cancelFn - 关闭 did-connect 时,执行的回调
303
- * @returns
304
- */
305
- const wrapOnceEmit = useMemoizedFn((eventName, doneFn, cancelFn) => {
306
- const fnKeyMap = {
307
- login: [EVENTS.LOGIN, EVENTS.CANCEL_LOGIN],
308
- 'bind-wallet': [EVENTS.BIND_WALLET, EVENTS.CANCEL_BIND_WALLET],
309
- 'switch-passport': [EVENTS.SWITCH_PASSPORT, EVENTS.CANCEL_SWITCH_PASSPORT],
310
- 'switch-profile': [EVENTS.SWITCH_PROFILE, EVENTS.CANCEL_SWITCH_PROFILE],
311
- // HACK: 对于内部来说,switch-did 就是 login,所以内部的监听事件仍然是 login
312
- 'switch-did': [EVENTS.LOGIN, EVENTS.CANCEL_LOGIN],
313
- // @FIXME: @zhanghan 整个事件发射的机制需要重构,目前的实现不是很优雅,对于用户来说不是很好用。 https://github.com/ArcBlock/ux/pull/1414#discussion_r1904830954
314
- 'did-space-connected': [EVENTS.DID_SPACE_CONNECTED],
315
- };
316
-
317
- if (!Object.keys(fnKeyMap).includes(eventName)) {
318
- return;
319
- }
320
-
321
- const listenFn = async (...args) => {
322
- if (isFunction(doneFn)) {
323
- await doneFn(...args);
324
- }
325
- events.emit(eventName, ...args);
326
- };
327
- events.once(fnKeyMap[eventName][0], listenFn);
328
- events.once(fnKeyMap[eventName][1], async (...args) => {
329
- if (isFunction(listenFn)) {
330
- events.off(fnKeyMap[eventName][0], listenFn);
331
- }
332
- if (isFunction(cancelFn)) {
333
- await cancelFn(...args);
334
- }
335
- events.emit(`cancel-${eventName}`, ...args);
336
- });
337
- });
338
-
339
- const prevInitialized = usePrevious(state.initialized, (prev, next) => {
340
- if (prev !== next) {
341
- return true;
342
- }
343
- if (prev === true && next === true) {
344
- return true;
345
- }
346
- return false;
347
- });
348
-
349
- const {
350
- syncSessionSate,
351
- handleRefreshUser,
352
- handleRefreshToken,
353
- renewToken,
354
- clearSession,
355
- handleLoginResult,
356
- decrypt,
357
- service,
358
- getSessionToken,
359
- getRefreshToken,
360
- setRefreshToken,
361
- setSessionToken,
362
- } = useSessionToken({
363
- state,
364
- pageState,
365
- sessionTokenStorage,
366
- refreshTokenStorage,
367
- csrfTokenStorage,
368
- serviceHost: props.serviceHost,
369
- apiOptions: props.apiOptions,
370
- lazyRefreshToken: props.lazyRefreshToken,
371
- onRefresh({ type, sessionToken, refreshToken, user }) {
372
- debug('onRefresh', { type, user });
373
-
374
- if (globalThis.blocklet) {
375
- const data = {
376
- did: user.did,
377
- host: window.location.host,
378
- appPid: globalThis.blocklet.appPid,
379
- visitorId: getVisitorId(),
380
- sourceAppPid: user.sourceAppPid,
381
- fullName: user.fullName,
382
- };
383
- if (type === 'refreshToken') {
384
- debug('bridge callArc: onRefreshToken');
385
- bridge.callArc('onRefreshToken', {
386
- sessionToken,
387
- refreshToken,
388
- user: data,
389
- });
390
- } else if (type === 'refreshUser') {
391
- debug('bridge callArc: onRefreshUser');
392
- bridge.callArc('onRefreshUser', {
393
- sessionToken,
394
- refreshToken,
395
- user: data,
396
- });
397
- }
398
- }
399
- },
400
- });
401
-
402
- const handleVerify = useVerify({ state, currentLocale, connectApi });
403
-
404
- const loginWallet = useMemoizedFn((done, extraParams = {}, options = {}) => {
405
- stopOnOpen();
406
- const params = done;
407
- if (typeof params === 'object') {
408
- done = params.onSuccess; // eslint-disable-line no-param-reassign
409
- extraParams = params.extraParams || {}; // eslint-disable-line no-param-reassign
410
- options = params.options || {}; // eslint-disable-line no-param-reassign
411
- // FIXME: @zhanghan 将来需要移除这种传递方式,配置参数统一放到 options 中
412
- options.origin = params.origin || ''; // eslint-disable-line no-param-reassign
413
- }
414
-
415
- if (!state.user || options.origin === 'switch-did') {
416
- if (options.origin === 'switch-did') {
417
- wrapOnceEmit('switch-did', done, params?.onCancel);
418
- } else {
419
- wrapOnceEmit('login', done, params?.onCancel);
420
- }
421
-
422
- pageState.extraParams = extraParams;
423
- pageState.options = options;
424
- state.action = 'login';
425
- openConnect();
426
- }
427
- });
428
-
429
- // 高阶函数包装器 - 二次认证装饰器
430
- const withSecondaryAuth = useMemoizedFn((fn, options = {}) => {
431
- // 保存原始函数和上下文
432
- const originalFn = fn;
433
-
434
- return function wrappedSecondaryAuth(...args) {
435
- // 保存调用时的 this 上下文
436
- const context = this;
437
-
438
- return (async () => {
439
- try {
440
- const result = await handleVerify(options);
441
- if (!result.sessionId) {
442
- throw new Error('Authentication failed');
443
- }
444
- // 使用 call 方法确保 this 指向正确
445
- return await originalFn.call(context, result, ...args);
446
- } catch (error) {
447
- // 如果是用户取消认证,不需要显示错误信息
448
- console.error('Authentication failed', error);
449
- throw error;
450
- }
451
- })();
452
- };
453
- });
454
- /**
455
- * did-connect 登录函数
456
- * @type {import('../types').LoginSessionFn}
457
- */
458
- const login = useMemoizedFn(async (done, extraParams = {}, options = {}) => {
459
- stopOnOpen();
460
- const params = done;
461
- if (typeof params === 'object') {
462
- done = params.onSuccess; // eslint-disable-line no-param-reassign
463
- extraParams = params.extraParams || {}; // eslint-disable-line no-param-reassign
464
- options = params.options || {}; // eslint-disable-line no-param-reassign
465
- // FIXME: @zhanghan 移除了 params.onCancel 回调,需要观察是否会导致别的问题,目前未找到任何地方使用了 onCancel 回调
466
- }
467
-
468
- if (isUndefined(extraParams?.inviter) && window.localStorage.getItem('inviter')) {
469
- extraParams.inviter = window.localStorage.getItem('inviter');
470
- }
471
-
472
- // 使用 Url 中编码的参数
473
- const { params: decodedParams } = await decodeUrlParams();
474
- if (isUndefined(extraParams?.forceConnected) && decodedParams?.forceConnected) {
475
- extraParams.forceConnected = decodedParams.forceConnected;
476
- if (isUndefined(options.showQuickConnect)) {
477
- options.showQuickConnect = false;
478
- }
479
- }
480
- if (isUndefined(extraParams?.sourceAppPid) && !isUndefined(decodedParams?.sourceAppPid)) {
481
- extraParams.sourceAppPid = decodedParams.sourceAppPid;
482
- }
483
- if (extraParams?.openMode === 'redirect') {
484
- const redirect = extraParams?.redirect || '/';
485
- const url = new URL(`${BLOCKLET_SERVICE_PATH_PREFIX}/login`, window.location.origin);
486
- url.searchParams.set('redirect', redirect);
487
- if (extraParams?.forceConnected) {
488
- url.searchParams.set('forceConnected', extraParams?.forceConnected);
489
- }
490
- if (!isUndefined(extraParams?.sourceAppPid)) {
491
- url.searchParams.set('sourceAppPid', extraParams?.sourceAppPid);
492
- }
493
- if (!isUndefined(extraParams?.inviter)) {
494
- url.searchParams.set('inviter', extraParams?.inviter);
495
- }
496
- if (options?.origin) {
497
- url.searchParams.set('origin', options.origin);
498
- }
499
- // 这里是安全的
500
- window.location.href = url.href;
501
- return;
502
- }
503
-
504
- let sourceAppPid = extraParams?.sourceAppPid;
505
- if (isUndefined(sourceAppPid) && federatedEnabled) {
506
- sourceAppPid = masterSite.appPid;
507
- }
508
- // 必须等到初始化完成后才能执行
509
- await pWaitFor(() => state.initialized);
510
-
511
- // @compatible: 兼容旧的传递 origin 的方式
512
- if (typeof options === 'string') {
513
- // eslint-disable-next-line no-param-reassign
514
- options = { origin: options };
515
- }
516
- // @compatible end
517
-
518
- const copyExtraParams = cloneDeep(extraParams);
519
- copyExtraParams.passkeyBehavior = 'both';
520
-
521
- // if (!isUndefined(copyExtraParams.allowWallet)) {
522
- // pageState.allowWallet = copyExtraParams.allowWallet;
523
- // delete copyExtraParams.allowWallet;
524
- // } else {
525
- // pageState.allowWallet = undefined;
526
- // }
527
- pageState.allowWallet = undefined;
528
-
529
- const openMode = copyExtraParams?.openMode || props?.extraParams?.openMode;
530
-
531
- if (!isUndefined(openMode)) {
532
- pageState.openMode = openMode;
533
- // 如果处于统一登录模式下,并且当前应用不是 master,才需要使用 window.open 的方式
534
- } else if (sourceAppPid) {
535
- if (sourceAppPid !== globalThis.blocklet.appPid && getFederatedEnabled(globalThis.blocklet)) {
536
- pageState.openMode = 'window';
537
- }
538
- } else {
539
- pageState.openMode = 'popup';
540
- }
541
-
542
- // 如果已经有跨站存储的权限,并且当前是 window 模式,则强制改为 popup 模式
543
- if (pageState.openMode === 'window' && !browser.arcSphere && !browser.wallet) {
544
- const result = await requestStorageAccess();
545
- if (result) {
546
- pageState.openMode = 'popup';
547
- }
548
- }
549
-
550
- if (sourceAppPid) {
551
- state.baseUrl = globalThis.blocklet.appUrl;
552
- } else {
553
- state.baseUrl = '';
554
- }
555
- loginWallet(done, copyExtraParams, options);
556
- });
557
-
558
- // eslint-disable-next-line require-await
559
- const logout = useMemoizedFn(async (done) => {
560
- const visitorId = getVisitorId();
561
-
562
- if (globalThis.blocklet) {
563
- // TODO: 需要增加 webapp 退出登录的处理
564
- sdk.user.logout({ visitorId }).catch((error) => {
565
- console.warn('Failed to logout remote', error);
566
- });
567
- await sleep(100);
568
- }
569
-
570
- // HACK: 仅在 blocklet 环境中才发起请求
571
- if (globalThis.blocklet) {
572
- const logoutData = {
573
- visitorId,
574
- appPid: globalThis.blocklet.appPid,
575
- userDid: state.user.did,
576
- };
577
- debug('bridge callArc: onLogout', logoutData);
578
- bridge.callArc('onLogout', logoutData);
579
- }
580
-
581
- debugTmp('[clearSession] createSessionContext -> logout:');
582
-
583
- clearSession();
584
- closeConnect();
585
- state.user = null;
586
- state.provider = '';
587
- state.walletOS = '';
588
- state.error = '';
589
- state.loading = false;
590
- events.emit('logout');
591
-
592
- ReactGA.set({
593
- user_id: undefined,
594
- });
595
-
596
- if (typeof done === 'function') {
597
- done();
598
- }
599
- });
600
-
601
- const {
602
- userSessions,
603
- refresh: refreshUserSessions,
604
- loaded: loadedUserSessions,
605
- loginUserSession,
606
- } = useQuickConnect({
607
- appPid: getCurrentAppPid(state.user),
608
- loginAppPid: globalThis?.blocklet?.appPid,
609
- sourceAppPid: state?.user?.sourceAppPid,
610
- autoFetch: false,
611
- fetchAll: true,
612
- });
613
-
614
- const getMessageByBlocklet = useMemoizedFn((action, locale) => {
615
- if (!globalThis.blocklet) return null;
616
-
617
- const messages = globalThis.blocklet?.settings?.actionConfig?.[action];
618
- if (messages) {
619
- return messages[locale] || messages.en;
620
- }
621
- return null;
622
- });
623
-
624
- const ensureLogin = useMemoizedFn(async () => {
625
- if (!state?.user) {
626
- await new Promise((resolve) => {
627
- login(() => {
628
- resolve();
629
- });
630
- });
631
- }
632
- });
633
-
634
- // HACK: 先保留这段代码,后续改版时删除
635
- // const cleanConnectedState = useMemoizedFn(() => {
636
- // const cookieOptions = getCookieOptions({ returnDomain: false });
637
- // Cookie.remove('connected_did', cookieOptions);
638
- // Cookie.remove('connected_pk', cookieOptions);
639
- // Cookie.remove('connected_app', cookieOptions);
640
- // Cookie.remove('connected_wallet_os', cookieOptions);
641
- // });
642
-
643
- // See:
644
- // - https://github.com/ArcBlock/ux/issues/520
645
- // - https://github.com/ArcBlock/did-connect/issues/56
646
- const switchDid = useMemoizedFn((done, extraParams = {}, options = {}) => {
647
- stopOnOpen();
648
- const params = done;
649
- if (typeof params === 'object') {
650
- done = params.onSuccess; // eslint-disable-line no-param-reassign
651
- extraParams = params.extraParams || {}; // eslint-disable-line no-param-reassign
652
- options = params.options || {}; // eslint-disable-line no-param-reassign
653
- }
654
-
655
- // @compatible: 兼容旧的传递 origin 的方式
656
- if (typeof options === 'string') {
657
- // eslint-disable-next-line no-param-reassign
658
- options = { origin: options };
659
- }
660
- // @compatible end
661
-
662
- if (options?.userSession) {
663
- loginUserSession(options.userSession).then((result) => {
664
- updateConnectedInfo(
665
- {
666
- connected_did: options.userSession.user.did,
667
- connected_pk: options.userSession.user.pk,
668
- connected_wallet_os: options.userSession.extra.walletOS,
669
- connected_app: getAppId(),
670
- },
671
- true
672
- );
673
- wrapOnceEmit('switch-did', done);
674
- handleLoginResult({ ...result, encrypted: false });
675
- handleRefreshUser({ showProgress: true })
676
- .then(async () => {
677
- await sleep(200);
678
- events.emit(EVENTS.LOGIN, result, decrypt, session.current);
679
- notifyBridge(session.current);
680
- await connectToDidSpaceForFullAccess();
681
- })
682
- .catch((error) => {
683
- events.emit(EVENTS.LOGIN_FAILED, error);
684
- throw error;
685
- });
686
- });
687
- } else {
688
- if (isUndefined(extraParams?.forceConnected)) {
689
- // switchDid 时,禁用 forceConnected,以便切换到其他的钱包
690
- extraParams.forceConnected = false;
691
- }
692
- extraParams.passkeyBehavior = 'both';
693
- login(done, extraParams, { ...options, origin: 'switch-did' });
694
- }
695
- });
696
-
697
- const refreshProfile = useMemoizedFn(async () => {
698
- await sdk.user.refreshProfile();
699
- });
700
-
701
- const switchProfile = useMemoizedFn(async (done, extraParams = {}) => {
702
- await ensureLogin();
703
- stopOnOpen();
704
- const params = done;
705
- if (typeof params === 'object') {
706
- done = params.onSuccess; // eslint-disable-line no-param-reassign
707
- extraParams = params.extraParams || {}; // eslint-disable-line no-param-reassign
708
- }
709
- wrapOnceEmit('switch-profile', done, params?.onCancel);
710
- pageState.extraParams = extraParams;
711
- pageState.options = {};
712
- state.action = 'switch-profile';
713
- openConnect();
714
- });
715
-
716
- const switchPassport = useMemoizedFn(async (done, extraParams = {}) => {
717
- await ensureLogin();
718
- stopOnOpen();
719
- const params = done;
720
- if (typeof params === 'object') {
721
- done = params.onSuccess; // eslint-disable-line no-param-reassign
722
- extraParams = params.extraParams || {}; // eslint-disable-line no-param-reassign
723
- }
724
- wrapOnceEmit('switch-passport', done, params?.onCancel);
725
-
726
- pageState.extraParams = extraParams;
727
- pageState.options = {};
728
- state.action = 'switch-passport';
729
- openConnect();
730
- });
731
-
732
- const connectToDidSpace = useMemoizedFn(async ({ extraParams = {}, onSuccess = () => {}, ...rest } = {}) => {
733
- await ensureLogin();
734
- stopOnOpen();
735
-
736
- pageState.extraParams = {
737
- referrer: window.location.href,
738
- purpose: 'authorize-for-import',
739
- ...extraParams,
740
- saveConnect: false, // 连接 spaces 不保存连接信息
741
- };
742
- pageState.options = {};
743
- state.action = 'connect-to-did-spaces-for-user';
744
- openConnect({
745
- hideCloseButton: true,
746
- onSuccess,
747
- ...rest,
748
- });
749
- });
750
-
751
- /**
752
- * @description
753
- * @param {{
754
- * extraParams: Record<string, any>,
755
- * onSuccess: (response: Record<string, any>, decrypt: (value: string) => string),
756
- * }} [{ extraParams = {}, onSuccess = {} }={}]
757
- */
758
- const connectToDidSpaceForImport = useMemoizedFn(async ({ extraParams = {}, onSuccess = {} } = {}) => {
759
- await ensureLogin();
760
- connectToDidSpace({
761
- extraParams: {
762
- // service 交互
763
- purpose: 'authorize-for-import',
764
- ...extraParams,
765
- },
766
- onSuccess,
767
- hideCloseButton: false,
768
- });
769
- });
770
-
771
- const { didSpacesGuideApi, DidSpacesGuideView } = useDIDSpacesGuide({
772
- autoClose: false,
773
- });
774
-
775
- const connectToDidSpaceForFullAccess = async ({ extraParams = {}, onSuccess = () => {} } = {}) => {
776
- await ensureLogin();
777
- if (!(await didSpacesIsRequired(state?.user))) {
778
- return;
779
- }
780
- didSpacesGuideApi.open();
781
- didSpacesGuideApi.onConnect((callback = noop) => {
782
- const popupProps = {
783
- prefix: '/connect-to-did-space',
784
- custom: true,
785
- extraParams: {
786
- provider: 'wallet',
787
- appPid: window.blocklet?.appPid,
788
- appDid: window.blocklet?.appId,
789
- appName: window.blocklet?.appName,
790
- appDescription: window.blocklet?.appDescription,
791
- appUrl: window.blocklet?.appUrl,
792
- referrer: window.location.href,
793
- connectScope: 'user',
794
- ...extraParams,
795
- },
796
- onSuccess: async (response, decryptFunc) => {
797
- const spaceGateway = decryptFunc ? decryptFunc(response.spaceGateway) : response.spaceGateway;
798
- await sdk.user.updateDidSpace({ spaceGateway });
799
- await handleRefreshUser({ showProgress: true });
800
- await onSuccess(response, decryptFunc);
801
- await didSpacesGuideApi.close();
802
- events.emit(EVENTS.DID_SPACE_CONNECTED, response, decryptFunc);
803
- if (globalThis.blocklet) {
804
- debug('bridge callArc: onDidSpaceConnected');
805
- bridge.callArc('onDidSpaceConnected');
806
- }
807
- callback(true);
808
- },
809
- onError: (error) => {
810
- console.error(error);
811
- Toast.error(error.message);
812
- didSpacesGuideApi.close();
813
- callback(false);
814
- },
815
- onClose: () => {
816
- callback();
817
- },
818
- };
819
-
820
- connectApi.openPopup(popupProps, {
821
- baseUrl: DID_SPACES_BASE_URL,
822
- });
823
- });
824
- };
825
-
826
- const bindWallet = useMemoizedFn(async (done, extraParams = {}) => {
827
- await ensureLogin();
828
- if (getWalletDid(state.user)) {
829
- done({}, decrypt, session.current);
830
- return;
831
- }
832
- stopOnOpen();
833
- const params = done;
834
- if (typeof params === 'object') {
835
- done = params.onSuccess; // eslint-disable-line no-param-reassign
836
- }
837
- wrapOnceEmit('bind-wallet', done, params?.onCancel);
838
- pageState.extraParams = extraParams;
839
- pageState.options = {};
840
- state.action = 'bind-wallet';
841
- openConnect();
842
- });
843
-
844
- const onSwitchPassport = useMemoizedFn(async (result) => {
845
- debug('onSwitchPassport', { result });
846
- pageState.extraParams = {};
847
- pageState.options = {};
848
- handleLoginResult(result);
849
- state.loading = false;
850
- await handleRefreshUser({ showProgress: true });
851
- await sleep(200);
852
- events.emit(EVENTS.SWITCH_PASSPORT, result, decrypt, session.current);
853
- if (globalThis.blocklet) {
854
- debug('bridge callArc: onSwitchPassport');
855
- bridge.callArc('onSwitchPassport');
856
- }
857
- });
858
-
859
- const onSwitchPassportFailed = useMemoizedFn((error) => {
860
- events.emit(EVENTS.SWITCH_PASSPORT_FAILED, error);
861
- });
862
-
863
- const onLogin = useMemoizedFn(async (result) => {
864
- if (state.action === 'switch-passport') {
865
- onSwitchPassport(result);
866
- return;
867
- }
868
-
869
- debug('onLogin', { result });
870
- pageState.extraParams = {};
871
- pageState.options = {};
872
- handleLoginResult(result);
873
- state.loading = false;
874
- await handleRefreshUser({ showProgress: true });
875
- await sleep(200);
876
- if (state.action === 'login') {
877
- debug('onLogin: emit LOGIN event', { result });
878
- events.emit(EVENTS.LOGIN, result, decrypt, session.current);
879
- notifyBridge(session.current);
880
- connectToDidSpaceForFullAccess();
881
- }
882
- });
883
-
884
- const onLoginFailed = useMemoizedFn((error) => {
885
- events.emit(EVENTS.LOGIN_FAILED, error);
886
- });
887
-
888
- const onBindWallet = useMemoizedFn((result) => {
889
- pageState.extraParams = {};
890
- pageState.options = {};
891
- handleLoginResult(result);
892
- state.loading = false;
893
- handleRefreshUser({ showProgress: true }).then(async () => {
894
- await sleep(100);
895
- events.emit(EVENTS.BIND_WALLET, result, decrypt, session.current);
896
- if (globalThis.blocklet) {
897
- debug('bridge callArc: onBindWallet');
898
- bridge.callArc('onBindWallet');
899
- }
900
- });
901
- // NOTICE: auth0 绑定 wallet 在新的版本中,不会再要求切换 login_token,这里的处理是为了兼容有无 sessionToken 的两种情况,都可以顺利完成当前绑定操作
902
- });
903
-
904
- const onBindWalletFailed = useMemoizedFn((error) => {
905
- events.emit(EVENTS.BIND_WALLET_FAILED, error, session);
906
- });
907
-
908
- const onSwitchProfile = useMemoizedFn((result) => {
909
- debug('onSwitchProfile', { result });
910
- pageState.extraParams = {};
911
- pageState.options = {};
912
- state.loading = false;
913
- handleRefreshUser({ showProgress: true }).then(async () => {
914
- await sleep(100);
915
- events.emit(EVENTS.SWITCH_PROFILE, result, decrypt, session.current);
916
- if (globalThis.blocklet) {
917
- debug('bridge callArc: onSwitchProfile');
918
- bridge.callArc('onSwitchProfile');
919
- }
920
- });
921
- });
922
-
923
- const onClose = useMemoizedFn((action) => {
924
- pageState.extraParams = {};
925
- pageState.options = {};
926
- closeConnect();
927
- const fnMap = {
928
- login: EVENTS.CANCEL_LOGIN,
929
- 'switch-profile': EVENTS.CANCEL_SWITCH_PROFILE,
930
- 'switch-passport': EVENTS.CANCEL_SWITCH_PASSPORT,
931
- 'bind-wallet': EVENTS.CANCEL_BIND_WALLET,
932
- };
933
- const fnCancelMap = {
934
- login: 'offLogin',
935
- 'switch-profile': 'offSwitchProfile',
936
- 'switch-passport': 'offSwitchPassport',
937
- 'bind-wallet': 'offBindWallet',
938
- };
939
- events.emit(fnMap[action]);
940
-
941
- if (globalThis.blocklet) {
942
- debug(`bridge callArc: ${fnCancelMap[action]}`);
943
- bridge.callArc(`${fnCancelMap[action]}`);
944
- }
945
- });
946
-
947
- const onSuccess = useMemoizedFn((action, ...args) => {
948
- try {
949
- // 跳转
950
- callbacks[action](...args);
951
- } catch (err) {
952
- logger.error('Catch error in onSuccess', {
953
- action,
954
- args,
955
- err,
956
- });
957
- callbacks[`${action}-failed`]?.(err, ...args);
958
- }
959
- });
960
-
961
- const onError = useMemoizedFn((action, err, ...args) => {
962
- callbacks[`${action}-failed`]?.(err, ...args);
963
- });
964
-
965
- const {
966
- federatedMaster,
967
- federatedEnabled,
968
- master: masterSite,
969
- } = useFederated({
970
- locale: currentLocale,
971
- decrypt,
972
- login: loginWallet,
973
- handleLoginResult,
974
- handleRefreshUser,
975
- setRefreshToken,
976
- setSessionToken,
977
- });
978
-
979
- const { checkMatch } = useProtectedRoutes({ protectedRoutes: props.protectedRoutes || [] });
980
-
981
- const latestUserSessions = useLatest(userSessions);
982
-
983
- const getUserSessions = useMemoizedFn(async () => {
984
- if (!loadedUserSessions) {
985
- await refreshUserSessions();
986
- await sleep(100);
987
- }
988
- return latestUserSessions.current;
989
- });
990
-
991
- const closeConnect = useMemoizedFn(() => {
992
- state.open = false;
993
- connectApi.close();
994
- });
995
-
996
- /**
997
- * 打开 did-connect
998
- * @param {object} openConnectOptions
999
- * @param {boolean} openConnectOptions.hideCloseButton - 是否隐藏关闭按钮
1000
- * @param {function} openConnectOptions.onSuccess - 成功回调
1001
- */
1002
- const openConnect = async (openConnectOptions = {}) => {
1003
- let localeAction = state.action;
1004
- if (pageState?.options?.origin === 'switch-did') {
1005
- localeAction = pageState?.options?.origin;
1006
- }
1007
-
1008
- const defaultMessages = translations[localeAction];
1009
-
1010
- const messages = getMessageByBlocklet(localeAction, currentLocale);
1011
-
1012
- // 使用对象合并,确保即使前面的对象缺少某些key,也能从后面的对象中获取
1013
- const connectMessage = mergeIgnoreEmpty(defaultMessages[currentLocale] || defaultMessages.en, messages || {});
1014
- const extraParams = {
1015
- ...opts.extraParams,
1016
- ...props.extraParams,
1017
- ...pageState.extraParams,
1018
- previousUserDid: state?.user?.did,
1019
- email: state?.user?.email,
1020
- };
1021
-
1022
- if (localeAction === 'switch-did' && extraParams?.forceConnected) {
1023
- const t = (key, data = {}) => {
1024
- return translate(translations['switch-specified-did'], key, currentLocale, 'en', data);
1025
- };
1026
- connectMessage.title = t('title');
1027
- connectMessage.scan = t('scan', { did: extraParams.forceConnected });
1028
- connectMessage.confirm = t('confirm');
1029
- connectMessage.success = t('success');
1030
- }
1031
- // HACK: 部分参数不用传递给 server 端,需要手动移除
1032
-
1033
- delete extraParams?.openMode;
1034
- const params = {
1035
- action: state.action,
1036
- locale: currentLocale,
1037
- hideCloseButton: openConnectOptions?.hideCloseButton,
1038
- extraParams,
1039
- options: { ...(props.options || {}), ...pageState.options },
1040
- checkTimeout: props.timeout,
1041
- webWalletUrl: props.webWalletUrl,
1042
- messages: connectMessage,
1043
- useSocket: props.useSocket,
1044
- ...omit(props, [
1045
- 'action',
1046
- 'locale',
1047
- // NOTICE: 将 serviceHost 也透传给 DID Connect 组件
1048
- // 'serviceHost',
1049
- 'appendAuthServicePrefix',
1050
- 'autoDisconnect',
1051
- 'children',
1052
- 'timeout',
1053
- 'extraParams',
1054
- 'options',
1055
- 'webWalletUrl',
1056
- 'messages',
1057
- 'useSocket',
1058
- 'lazyRefreshToken',
1059
- 'apiOptions',
1060
- 'protectedRoutes',
1061
- ]),
1062
- // 允许 login/switchProfile/switchDid 的时候传入自定义参数
1063
- ...pick(pageState.extraParams || {}, [
1064
- 'autoConnect',
1065
- 'forceConnected',
1066
- 'saveConnect',
1067
- 'useSocket',
1068
- 'passkeyBehavior',
1069
- ]),
1070
- // baseUrl: state.baseUrl || props.baseUrl || '',
1071
-
1072
- // 注意 prefix 经过了特殊处理, 优先级高于 "...rest", 所以放在其后
1073
- prefix: pageState.prefix,
1074
- allowWallet: !isUndefined(pageState.allowWallet)
1075
- ? pageState.allowWallet
1076
- : state.action !== 'login' ||
1077
- ['1', 'true', undefined, null].includes(globalThis.blocklet?.DID_CONNECT_ALLOW_WALLET),
1078
- onSuccess: async (...args) => {
1079
- onSuccess(state.action, ...args);
1080
- if (isFunction(openConnectOptions?.onSuccess)) {
1081
- await openConnectOptions.onSuccess(...args);
1082
- }
1083
- state.open = false;
1084
- },
1085
- onClose(...args) {
1086
- onClose(state.action, ...args);
1087
- },
1088
- onError(err) {
1089
- console.error(state.action, err);
1090
- onError(state.action, err, session.current);
1091
- },
1092
- };
1093
- state.open = true;
1094
-
1095
- if (
1096
- ['login'].includes(state.action) &&
1097
- pageState.openMode === 'window' &&
1098
- !(browser.wallet || browser.arcSphere)
1099
- ) {
1100
- try {
1101
- const mergeParams = {
1102
- ...params,
1103
- baseUrl: window.location.origin,
1104
- };
1105
- // HACK: 在某些情况下,prefix 中已经携带了域名,这时不能再拼接一次
1106
- if (!isUrl(pageState.prefix)) {
1107
- mergeParams.prefix = joinURL(window.location.origin, props.serviceHost, pageState.prefix);
1108
- }
1109
- if (!mergeParams.extraParams?.provider) {
1110
- mergeParams.extraParams.provider = state?.user?.provider || 'wallet';
1111
- }
1112
- await connectApi.openPopup(mergeParams, { locale: currentLocale });
1113
- } catch (err) {
1114
- if (err instanceof NotOpenError) {
1115
- closeConnect();
1116
- }
1117
- if (err.message === 'Timeout') {
1118
- Toast.error(didConnectTimeoutError[currentLocale] || didConnectTimeoutError.en);
1119
- } else {
1120
- console.error(err);
1121
- }
1122
- }
1123
- } else {
1124
- connectApi.open(params);
1125
- }
1126
- };
1127
-
1128
- const autoSwitchDid = useMemoizedFn(async () => {
1129
- // NOTICE: 此处获得的 trimedUrl 是当前 location.href 精简参数后的 url,无需进行安全处理
1130
- const { params: decodedParams, url: trimedUrl } = await decodeUrlParams();
1131
- if (decodedParams) {
1132
- const { switchBehavior, forceConnected, sourceAppPid, showClose } = decodedParams;
1133
-
1134
- if (forceConnected === state.user.did) {
1135
- window.history.replaceState(null, 'trim', trimedUrl);
1136
- } else if (switchBehavior === 'required') {
1137
- switchDid(() => {}, {
1138
- openMode: 'redirect',
1139
- redirect: trimedUrl,
1140
- sourceAppPid,
1141
- forceConnected,
1142
- });
1143
- } else if (switchBehavior === 'disabled') {
1144
- window.history.replaceState(null, 'trim', trimedUrl);
1145
- } else {
1146
- confirmApi.open({
1147
- title: translations.switchAccountDialog[currentLocale].title,
1148
- content: (
1149
- <>
1150
- <Typography variant="body1" sx={{ textAlign: 'center', mb: 2 }}>
1151
- {translations.switchAccountDialog[currentLocale].description}
1152
- </Typography>
1153
- <Box sx={{ display: 'flex', alignItems: 'center', color: 'grey.A700' }}>
1154
- <Box
1155
- sx={{
1156
- display: 'flex',
1157
- flexDirection: 'column',
1158
- alignItems: 'center',
1159
- gap: 1.5,
1160
- lineHeight: 1,
1161
- }}>
1162
- <Avatar did={state.user.did} shape="circle" variant="circle" size={80} />
1163
- <DID did={state.user.did} showAvatar={false} compact responsive={false} />
1164
- {translations.switchAccountDialog[currentLocale].currentAccount}
1165
- </Box>
1166
- <Box sx={{ display: 'flex', justifyContent: 'center', px: 2 }}>
1167
- <Box
1168
- component={Icon}
1169
- icon={KeyboardDoubleArrowRightRoundedIcon}
1170
- sx={{
1171
- fontSize: 24,
1172
- color: palette.grey[400],
1173
- animation: 'right 2.5s linear infinite',
1174
-
1175
- '@keyframes right': {
1176
- '0%': {
1177
- transform: 'translateX(-3px)',
1178
- opacity: 0,
1179
- },
1180
- '60%': {
1181
- transform: 'translateX(3px)',
1182
- opacity: 1,
1183
- },
1184
- '100%': {
1185
- transform: 'translateX(0px)',
1186
- opacity: 0,
1187
- },
1188
- },
1189
- }}
1190
- />
1191
- </Box>
1192
- <Box
1193
- sx={{
1194
- display: 'flex',
1195
- flexDirection: 'column',
1196
- alignItems: 'center',
1197
- gap: 1.5,
1198
- lineHeight: 1,
1199
- fontWeight: 500,
1200
- }}>
1201
- <Avatar did={forceConnected} shape="circle" variant="circle" size={80} />
1202
- <DID did={forceConnected} showAvatar={false} compact responsive={false} />
1203
- {translations.switchAccountDialog[currentLocale].nextAccount}
1204
- </Box>
1205
- </Box>
1206
- </>
1207
- ),
1208
- showCloseButton: showClose,
1209
- showCancelButton: false,
1210
- confirmButtonText: translations.switchAccountDialog[currentLocale].confirm,
1211
- async onConfirm(close) {
1212
- const userSessionList = await getUserSessions();
1213
- const findUserSession = userSessionList.find((x) => x.userDid === forceConnected);
1214
-
1215
- if (findUserSession) {
1216
- loginUserSession(findUserSession).then((result) => {
1217
- updateConnectedInfo(
1218
- {
1219
- connected_did: findUserSession.user.did,
1220
- connected_pk: findUserSession.user.pk,
1221
- connected_wallet_os: findUserSession.extra.walletOS,
1222
- connected_app: getAppId(),
1223
- },
1224
- true
1225
- );
1226
- handleLoginResult({ ...result, encrypted: false });
1227
- handleRefreshUser({ showProgress: true })
1228
- .then(async () => {
1229
- await sleep(200);
1230
- events.emit(EVENTS.LOGIN, result, decrypt, session.current);
1231
- notifyBridge(session.current);
1232
- await connectToDidSpaceForFullAccess();
1233
- })
1234
- .catch((error) => {
1235
- events.emit(EVENTS.LOGIN_FAILED, error);
1236
- throw error;
1237
- });
1238
- });
1239
- close();
1240
- return;
1241
- }
1242
- switchDid(
1243
- () => {
1244
- close();
1245
- window.location.replace(trimedUrl);
1246
- },
1247
- { sourceAppPid, forceConnected, enableSwitchApp: false },
1248
- { showQuickConnect: true }
1249
- );
1250
- },
1251
- });
1252
- }
1253
- }
1254
- });
1255
-
1256
- /**
1257
- * @type {import('../types').OpenDidConnect}
1258
- */
1259
- const openDidConnect = useMemoizedFn(async (params, options) => {
1260
- const requirements = Object.assign(
1261
- {
1262
- login: true,
1263
- bindWallet: true,
1264
- bindDidSpaces: false,
1265
- },
1266
- options?.requirements
1267
- );
1268
- const openMode = options?.openMode || 'popup';
1269
- if (requirements.login) {
1270
- await ensureLogin();
1271
- }
1272
- if (requirements.bindWallet) {
1273
- await new Promise((resolve) => {
1274
- bindWallet(() => {
1275
- resolve();
1276
- });
1277
- });
1278
- }
1279
- if (requirements.bindDidSpaces) {
1280
- if (!(await didSpacesIsRequired(state?.user))) {
1281
- await new Promise((resolve) => {
1282
- if (requirements.bindDidSpaces === 'full') {
1283
- connectToDidSpaceForFullAccess({
1284
- onSuccess() {
1285
- resolve();
1286
- },
1287
- });
1288
- } else if (requirements.bindDidSpaces === 'read') {
1289
- connectToDidSpaceForImport({
1290
- onSuccess() {
1291
- resolve();
1292
- },
1293
- });
1294
- }
1295
- });
1296
- }
1297
- }
1298
-
1299
- if (openMode === 'window') {
1300
- connectApi.openPopup(params, options);
1301
- } else if (openMode === 'popup') {
1302
- connectApi.open(params);
1303
- }
1304
- });
1305
-
1306
- useUpdateEffect(() => {
1307
- const count = Number(state.unReadCount || 0); // 避免 unReadCount 为 string 类型
1308
- setUnReadCount(count);
1309
- }, [state.unReadCount]);
1310
-
1311
- const removeMagicToken = useMemoizedFn(() => {
1312
- if (searchParams.get('magicToken')) {
1313
- searchParams.delete('magicToken');
1314
- }
1315
- if (searchParams.toString()) {
1316
- window.history.replaceState({}, '', `${window.location.pathname}?${searchParams.toString()}`);
1317
- }
1318
- });
1319
-
1320
- const verifyMagicToken = useMemoizedFn((magicToken) => {
1321
- return new Promise((resolve, reject) => {
1322
- connectApi.open({
1323
- action: 'login',
1324
- locale: currentLocale,
1325
- prefix: pageState.prefix,
1326
- useSocket: false,
1327
- messages: {
1328
- title: currentLocale === 'zh' ? '验证 MagicLink Token' : 'Verify MagicLink Token',
1329
- },
1330
- magicToken,
1331
- onSuccess(...args) {
1332
- onLogin(...args);
1333
- removeMagicToken();
1334
- resolve();
1335
- },
1336
- onError(err) {
1337
- events.emit(EVENTS.LOGIN_FAILED, err);
1338
- reject(err);
1339
- },
1340
- });
1341
- });
1342
- });
1343
-
1344
- const currentSession = {
1345
- ...state,
1346
- loading: pageState.autoConnect ? !state.user || state.loading : state.loading,
1347
- openDidConnect,
1348
- login,
1349
- logout,
1350
- switchDid,
1351
- autoSwitchDid,
1352
- refreshProfile,
1353
- switchProfile,
1354
- switchPassport,
1355
- connectToDidSpaceForImport,
1356
- connectToDidSpaceForFullAccess,
1357
- bindWallet,
1358
- refresh: handleRefreshUser,
1359
- updateConnectedInfo,
1360
- // federated relates
1361
- federatedMaster,
1362
- // oauth relates
1363
- useOAuth,
1364
- OAuthProvider,
1365
- OAuthConsumer,
1366
- OAuthContext,
1367
- // passkey relates
1368
- usePasskey,
1369
- PasskeyProvider,
1370
- PasskeyConsumer,
1371
- PasskeyContext,
1372
- // user related
1373
- useDid,
1374
- WrapDid,
1375
- getUserSessions,
1376
- async loginUserSession(userSessionItem) {
1377
- try {
1378
- const loginResult = await loginUserSession(userSessionItem);
1379
- updateConnectedInfo(
1380
- {
1381
- connected_did: userSessionItem.user.did,
1382
- connected_pk: userSessionItem.user.pk,
1383
- connected_wallet_os: userSessionItem.extra.walletOS,
1384
- connected_app: getAppId(),
1385
- },
1386
- true
1387
- );
1388
- handleLoginResult({ ...loginResult, encrypted: false });
1389
- await handleRefreshUser({ showProgress: true });
1390
- await sleep(200);
1391
- events.emit(EVENTS.LOGIN, loginResult, decrypt, session.current);
1392
- notifyBridge(session.current);
1393
- await connectToDidSpaceForFullAccess();
1394
- } catch (err) {
1395
- events.emit(EVENTS.LOGIN_FAILED, err);
1396
- throw err;
1397
- }
1398
- },
1399
- unReadCount,
1400
- setUnReadCount,
1401
-
1402
- verifyMagicToken,
1403
- // 二次认证相关方法
1404
- withSecondaryAuth,
1405
- };
1406
-
1407
- const session = useLatest(currentSession);
1408
-
1409
- const sessionValue = {
1410
- api: service,
1411
- events,
1412
- storage: sessionTokenStorage,
1413
- connectApi,
1414
- session: session.current,
1415
- };
1416
-
1417
- const callbacks = {
1418
- login: onLogin,
1419
- 'switch-profile': onSwitchProfile,
1420
- 'switch-passport': onSwitchPassport,
1421
- 'switch-passport-failed': onSwitchPassportFailed,
1422
- 'bind-wallet': onBindWallet,
1423
- 'bind-wallet-failed': onBindWalletFailed,
1424
- 'login-failed': onLoginFailed,
1425
- };
1426
-
1427
- // user change 事件
1428
- useUpdateEffect(() => {
1429
- if (state.initialized) {
1430
- const isFirst = !prevInitialized;
1431
- events.emit('change', { user: cloneDeep(state.user), isFirst });
1432
- if (state.user) {
1433
- if (globalThis.blocklet) {
1434
- const data = {
1435
- did: state.user.did,
1436
- host: window.location.host,
1437
- appPid: globalThis.blocklet.appPid,
1438
- visitorId: getVisitorId(),
1439
- sourceAppPid: state.user.sourceAppPid,
1440
- fullName: state.user.fullName,
1441
- };
1442
- debug('bridge callArc: onChange', {
1443
- isFirst,
1444
- user: data,
1445
- });
1446
- bridge.callArc('onChange', {
1447
- ...data,
1448
- user: data,
1449
- isFirst,
1450
- });
1451
- }
1452
- }
1453
- }
1454
- }, [state.user, state.initialized, events]);
1455
-
1456
- // 检查当前登录的用户与 url 中参数指定的用户是否一致
1457
- useUpdateEffect(() => {
1458
- if (state.initialized && state.user) {
1459
- autoSwitchDid();
1460
- }
1461
- }, [state.initialized]);
1462
-
1463
- useMount(async () => {
1464
- const bridgeOnLogin = (result) => {
1465
- if (isNil(result.encrypted)) {
1466
- result.encrypted = false;
1467
- }
1468
- if (window.temporaryDIDConnectOnSuccess instanceof Function) {
1469
- debug('bridgeOnLogin: temporaryDIDConnectOnSuccess', { result });
1470
- window.temporaryDIDConnectOnSuccess(result, (v) => v);
1471
- } else {
1472
- debug('bridgeOnLogin: onLogin', { result });
1473
- onLogin(result);
1474
- }
1475
- };
1476
- bridge.registerBlocklet('callLoginOAuth', (options) => {
1477
- debug('bridge registerBlocklet: callLoginOAuth', options);
1478
- connectApi.loginOAuth({
1479
- ...options,
1480
- onLogin: bridgeOnLogin,
1481
- });
1482
- });
1483
- bridge.registerBlocklet('logout', logout);
1484
- // NOTICE: login 是指 arcsphere 已经完成登录,传回的 result 是登录后的数据
1485
- bridge.registerBlocklet('login', (result) => {
1486
- debug('bridge registerBlocklet: login', result);
1487
- bridgeOnLogin(result);
1488
- });
1489
-
1490
- // 确保 普通浏览器中 visitorId 存在
1491
- ensureVisitorId();
1492
- const visitorId = getVisitorId();
1493
-
1494
- if (!visitorId) {
1495
- // 确保 ArcSphere 中 visitorId 存在
1496
- if (browser.arcSphere) {
1497
- debug('bridge callArc: getVisitorId');
1498
- const walletVisitorId = await bridge.callArc('getVisitorId');
1499
- debug('bridge callArc: getVisitorId result', { walletVisitorId });
1500
- setVisitorId(walletVisitorId);
1501
- } else if (browser.wallet) {
1502
- const walletVisitorId = await getMobileVisitorId();
1503
- setVisitorId(walletVisitorId);
1504
- }
1505
- }
1506
-
1507
- // ensure we are in the same app
1508
- const connectedApp = Cookie.get('connected_app');
1509
- const actualApp = getAppId();
1510
-
1511
- if (props.autoDisconnect && connectedApp && actualApp && connectedApp !== actualApp) {
1512
- debugTmp('[clearSession] createSessionContext -> SessionProvider -> useMount: autoDisconnect', {
1513
- autoDisconnect: props.autoDisconnect,
1514
- connectedApp,
1515
- actualApp,
1516
- });
1517
- clearSession();
1518
- state.initialized = true;
1519
- if (pageState.autoConnect) {
1520
- openConnect();
1521
- } else {
1522
- closeConnect();
1523
- }
1524
- return;
1525
- }
1526
-
1527
- const sessionToken = getSessionToken();
1528
-
1529
- // HACK: 如果页面处于 iframe 中,将不进行自动登录的逻辑,防止页面会产生两次钱包的自动登录流程
1530
- if (window?.self === window?.parent) {
1531
- let platform = 'web';
1532
- if (browser.mobile.apple.device) {
1533
- platform = 'ios';
1534
- } else if (browser.mobile.android.device) {
1535
- platform = 'android';
1536
- }
1537
- if (browser.wallet && checkEnableAutoLogin({ version: browser.walletVersion, platform })) {
1538
- if (!sessionToken) {
1539
- try {
1540
- state.loading = true;
1541
- const result = await loginInMobile();
1542
- if (result) {
1543
- await onLogin({
1544
- sessionToken: result.sessionToken,
1545
- refreshToken: result.refreshToken,
1546
- csrfToken: result.csrfToken,
1547
- visitorId: result.visitorId,
1548
- encrypted: false,
1549
- });
1550
- state.initialized = true;
1551
- return;
1552
- }
1553
- } catch (err) {
1554
- events.emit(EVENTS.LOGIN_FAILED, err);
1555
- throw err;
1556
- } finally {
1557
- state.loading = false;
1558
- }
1559
- }
1560
- }
1561
- }
1562
-
1563
- const magicToken = searchParams.get('magicToken');
1564
- if (magicToken) {
1565
- try {
1566
- state.initialized = true;
1567
- await verifyMagicToken(magicToken);
1568
- } catch (err) {
1569
- console.error('verifyMagicToken failed', err);
1570
- }
1571
- }
1572
-
1573
- if (sessionToken) {
1574
- await handleRefreshUser({ showProgress: true });
1575
- state.initialized = true;
1576
-
1577
- if (state.user) {
1578
- await connectToDidSpaceForFullAccess();
1579
- }
1580
- return;
1581
- }
1582
-
1583
- if (typeof window !== 'undefined') {
1584
- // If a login token exist in url, set that token in storage
1585
- const url = new URL(window.location.href);
1586
- const loginToken = url.searchParams.get('loginToken');
1587
- if (loginToken) {
1588
- handleLoginResult({ loginToken, encrypted: false });
1589
-
1590
- url.searchParams.delete('loginToken');
1591
- window.history.replaceState({}, window.title, url.href);
1592
- return;
1593
- }
1594
- }
1595
-
1596
- // If a refresh token exist and session token NOT exist, do refresh session
1597
- const refreshToken = getRefreshToken();
1598
- if (refreshToken) {
1599
- await handleRefreshToken(true);
1600
- state.initialized = true;
1601
- return;
1602
- }
1603
-
1604
- try {
1605
- // await tryAutoLoginFederated();
1606
- // } catch (err) {
1607
- // console.error('Federated: tryAutoLogin failed', err);
1608
- } finally {
1609
- state.initialized = true;
1610
- }
1611
-
1612
- if (pageState.autoConnect) {
1613
- openConnect();
1614
- } else {
1615
- closeConnect();
1616
- }
1617
-
1618
- if (
1619
- browser.arcSphere &&
1620
- ![
1621
- // 不应该唤起自动登录的路由
1622
- joinURL(BLOCKLET_SERVICE_PATH_PREFIX, 'login'),
1623
- joinURL(BLOCKLET_SERVICE_PATH_PREFIX, 'connect'),
1624
- joinURL(BLOCKLET_SERVICE_PATH_PREFIX, 'lost-passport'),
1625
- ].includes(window.location.pathname)
1626
- ) {
1627
- if (!sessionToken) {
1628
- try {
1629
- debug('bridge callArc: autoLogin');
1630
- const result = await bridge.callArc('autoLogin');
1631
- debug('bridgecallArc: autoLogin result', result);
1632
- if (result) {
1633
- await onLogin({
1634
- sessionToken: result.sessionToken,
1635
- refreshToken: result.refreshToken,
1636
- csrfToken: result.csrfToken,
1637
- visitorId: result.visitorId,
1638
- encrypted: false,
1639
- });
1640
- }
1641
- } catch (err) {
1642
- events.emit(EVENTS.LOGIN_FAILED, err);
1643
- logger.error('Failed to autoLogin ArcSphere', err);
1644
- }
1645
- }
1646
- }
1647
- });
1648
- const seamless = searchParams.has('popup');
1649
-
1650
- if (checkMatch()) {
1651
- if (!state.initialized) {
1652
- return <Center>{seamless ? null : <CircularProgress />}</Center>;
1653
- }
1654
- }
1655
-
1656
- const providerProps = {
1657
- locale: currentLocale,
1658
- onSwitchPassport: ({ sessionToken, refreshToken, csrfToken }) =>
1659
- onSwitchPassport({ sessionToken, refreshToken, csrfToken, encrypted: false }),
1660
- onUnbindOAuth: () => handleRefreshUser(),
1661
- onBindOAuth: () => handleRefreshUser(),
1662
- onAddPasskey: () => handleRefreshUser(),
1663
- onRemovePasskey: () => handleRefreshUser(),
1664
- session: session.current,
1665
- };
1666
-
1667
- return (
1668
- <Provider value={sessionValue}>
1669
- <PasskeyProvider {...providerProps}>
1670
- <OAuthProvider {...providerProps}>
1671
- {!state.open && typeof props.children === 'function' ? props.children(state) : props.children}
1672
- {connectHolder}
1673
- {confirmHolder}
1674
- {DidSpacesGuideView}
1675
- {opts.rolling && <WindowFocusAware callback={() => renewToken()} />}
1676
- <WindowFocusAware callback={syncSessionSate} />
1677
- </OAuthProvider>
1678
- </PasskeyProvider>
1679
- </Provider>
1680
- );
1681
- }
1682
-
1683
- SessionProvider.propTypes = {
1684
- children: PropTypes.any.isRequired,
1685
- serviceHost: PropTypes.string,
1686
- action: PropTypes.string,
1687
- prefix: PropTypes.string,
1688
- appendAuthServicePrefix: PropTypes.bool,
1689
- locale: PropTypes.string,
1690
- timeout: PropTypes.number,
1691
- autoConnect: PropTypes.bool, // should we open connect dialog when session not found
1692
- autoDisconnect: PropTypes.bool, // should we auto disconnect on appId mismatch
1693
- useSocket: PropTypes.bool, // should we auto disconnect on appId mismatch
1694
- extraParams: PropTypes.object,
1695
- options: PropTypes.object,
1696
- webWalletUrl: PropTypes.string,
1697
- protectedRoutes: PropTypes.arrayOf(PropTypes.string),
1698
- apiOptions: PropTypes.object,
1699
- lazyRefreshToken: PropTypes.bool,
1700
- };
1701
-
1702
- function withSession(Component) {
1703
- return function WithSessionComponent(props) {
1704
- return (
1705
- <ToastProvider>
1706
- <Consumer>{(sessionProps) => <Component {...props} {...sessionProps} />}</Consumer>
1707
- </ToastProvider>
1708
- );
1709
- };
1710
- }
1711
-
1712
- return {
1713
- SessionProvider: (props) => {
1714
- return (
1715
- <ToastProvider>
1716
- <FederatedProvider>
1717
- <SessionProvider {...props} />
1718
- </FederatedProvider>
1719
- </ToastProvider>
1720
- );
1721
- },
1722
- SessionConsumer: Consumer,
1723
- SessionContext,
1724
- withSession,
1725
- };
1726
- }
1727
-
1728
- function createAuthServiceSessionContext({ storageEngine = 'cookie' } = {}) {
1729
- const storageKey = SESSION_TOKEN_STORAGE_KEY;
1730
- const refreshTokenStorageKey = REFRESH_TOKEN_STORAGE_KEY;
1731
-
1732
- // componentId
1733
- let componentId = null;
1734
- if (typeof window !== 'undefined') {
1735
- const blocklet = globalThis.blocklet || {};
1736
-
1737
- // FIXME: @zhanghan 当 connect 组件使用不同的 baseUrl 时,不应该直接从 window.blocklet 去取值
1738
- componentId = blocklet.componentId;
1739
- }
1740
-
1741
- if (storageEngine === 'cookie') {
1742
- let path = '/';
1743
- // FIXME: @zhanghan sessionToken 也应该从 jwt 中读取过期时间,而不是通过配置来判断?
1744
- let sessionTokenExpireInDays = 1;
1745
-
1746
- if (typeof window !== 'undefined') {
1747
- const env = window.env || {};
1748
- const blocklet = window.blocklet || {};
1749
-
1750
- // FIXME: @zhanghan 当 connect 组件使用不同的 baseUrl 时,不应该直接从 window.blocklet 去取值
1751
- path = env.groupPathPrefix || blocklet.groupPrefix || blocklet.prefix || '';
1752
- path = path.replace(/\/+$/, '');
1753
- path = path || '/';
1754
-
1755
- // for backward compatibility
1756
- // 不支持 refreshToken 的旧版 server 不会在 window.blocklet 中返回 serverVersion
1757
- if (blocklet.serverVersion) {
1758
- sessionTokenExpireInDays = formatCacheTtl(window.blocklet?.settings?.session?.cacheTtl, 1 / 24); // 1h
1759
- }
1760
- }
1761
-
1762
- return createSessionContext(
1763
- storageKey,
1764
- 'cookie',
1765
- {
1766
- path,
1767
- returnDomain: false,
1768
- expireInDays: sessionTokenExpireInDays,
1769
- },
1770
- {
1771
- appendAuthServicePrefix: true,
1772
- extraParams: { componentId },
1773
- refreshTokenStorageKey,
1774
- }
1775
- );
1776
- }
1777
-
1778
- if (storageEngine === 'localStorage') {
1779
- return createSessionContext(storageKey, 'ls', {}, true, {
1780
- appendAuthServicePrefix: true,
1781
- extraParams: { componentId },
1782
- });
1783
- }
1784
-
1785
- throw new Error('storageEngine must be cookie or localStorage');
1786
- }
1787
-
1788
- export { SessionContext, createSessionContext, createAuthServiceSessionContext };
1789
- export default createSessionContext;