@arcblock/did-connect-react 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +134 -0
  3. package/lib/Address/index.js +4 -0
  4. package/lib/Avatar/index.js +4 -0
  5. package/lib/Button/index.js +17 -0
  6. package/lib/Connect/assets/locale.js +143 -0
  7. package/lib/Connect/assets/login-bg.png +0 -0
  8. package/lib/Connect/assets/login-slogan.js +9 -0
  9. package/lib/Connect/components/action-button.js +26 -0
  10. package/lib/Connect/components/app-tips.js +132 -0
  11. package/lib/Connect/components/auto-height.js +31 -0
  12. package/lib/Connect/components/back-button.js +24 -0
  13. package/lib/Connect/components/connect-status.js +263 -0
  14. package/lib/Connect/components/did-connect-title.js +126 -0
  15. package/lib/Connect/components/download-tips.js +52 -0
  16. package/lib/Connect/components/loading.js +26 -0
  17. package/lib/Connect/components/login-item/connect-choose-list.js +249 -0
  18. package/lib/Connect/components/login-item/login-method-item.js +129 -0
  19. package/lib/Connect/components/login-item/mobile-login-item.js +114 -0
  20. package/lib/Connect/components/login-item/passkey-login-item.js +44 -0
  21. package/lib/Connect/components/login-item/web-login-item.js +97 -0
  22. package/lib/Connect/components/mask-overlay.js +34 -0
  23. package/lib/Connect/components/refresh-overlay.js +57 -0
  24. package/lib/Connect/components/switch-app.js +70 -0
  25. package/lib/Connect/contexts/state.js +142 -0
  26. package/lib/Connect/fullpage.js +5 -0
  27. package/lib/Connect/hooks/auth-url.js +23 -0
  28. package/lib/Connect/hooks/method-list.js +46 -0
  29. package/lib/Connect/hooks/page-show.js +17 -0
  30. package/lib/Connect/hooks/security.js +27 -0
  31. package/lib/Connect/hooks/token.js +305 -0
  32. package/lib/Connect/hooks/use-apps.js +19 -0
  33. package/lib/Connect/hooks/use-quick-connect.js +97 -0
  34. package/lib/Connect/index.js +498 -0
  35. package/lib/Connect/landing-page.js +5 -0
  36. package/lib/Connect/plugins/email/index.js +62 -0
  37. package/lib/Connect/plugins/email/list-item.js +28 -0
  38. package/lib/Connect/plugins/email/placeholder.js +283 -0
  39. package/lib/Connect/plugins/index.js +4 -0
  40. package/lib/Connect/use-connect.js +164 -0
  41. package/lib/Connect/with-blocklet.js +15 -0
  42. package/lib/Connect/with-bridge-call.js +108 -0
  43. package/lib/Federated/context.js +61 -0
  44. package/lib/Federated/index.js +7 -0
  45. package/lib/Logo/index.js +4 -0
  46. package/lib/OAuth/context.js +234 -0
  47. package/lib/OAuth/guest.svg.js +5 -0
  48. package/lib/OAuth/index.js +7 -0
  49. package/lib/OAuth/passport-switcher.js +114 -0
  50. package/lib/Passkey/actions.js +165 -0
  51. package/lib/Passkey/constants.js +4 -0
  52. package/lib/Passkey/context.js +266 -0
  53. package/lib/Passkey/dialog.js +277 -0
  54. package/lib/Passkey/icon.js +13 -0
  55. package/lib/Passkey/index.js +9 -0
  56. package/lib/Service/index.js +62 -0
  57. package/lib/Session/assets/did-spaces-guide-cover.svg.js +135 -0
  58. package/lib/Session/assets/did-spaces-guide-icon.svg.js +9 -0
  59. package/lib/Session/context.js +5 -0
  60. package/lib/Session/did-spaces-guide.js +136 -0
  61. package/lib/Session/hooks/use-federated.js +64 -0
  62. package/lib/Session/hooks/use-mobile.js +8 -0
  63. package/lib/Session/hooks/use-protected-routes.js +11 -0
  64. package/lib/Session/hooks/use-session-token.js +169 -0
  65. package/lib/Session/hooks/use-verify.js +45 -0
  66. package/lib/Session/index.js +896 -0
  67. package/lib/Session/libs/constants.js +15 -0
  68. package/lib/Session/libs/did-spaces.js +10 -0
  69. package/lib/Session/libs/federated.js +42 -0
  70. package/lib/Session/libs/index.js +15 -0
  71. package/lib/Session/libs/locales.js +161 -0
  72. package/lib/Session/libs/login-mobile.js +55 -0
  73. package/lib/Session/window-focus-aware.js +17 -0
  74. package/lib/SessionManager/index.js +4 -0
  75. package/lib/Storage/engine/cookie.js +21 -0
  76. package/lib/Storage/engine/local-storage.js +36 -0
  77. package/lib/Storage/index.js +23 -0
  78. package/lib/User/index.js +6 -0
  79. package/lib/User/use-did.js +59 -0
  80. package/lib/User/wrap-did.js +13 -0
  81. package/lib/WebWalletSWKeeper/index.js +5 -0
  82. package/lib/constant.js +22 -0
  83. package/lib/error.js +8 -0
  84. package/lib/hooks/use-locale.js +7 -0
  85. package/lib/index.js +33 -0
  86. package/lib/locales/en.js +17 -0
  87. package/lib/locales/index.js +10 -0
  88. package/lib/locales/zh.js +17 -0
  89. package/lib/package.json.js +7 -0
  90. package/lib/types.d.ts +355 -0
  91. package/lib/utils.js +214 -0
  92. package/package.json +84 -0
  93. package/src/Address/index.jsx +2 -0
  94. package/src/Avatar/index.jsx +2 -0
  95. package/src/Button/Button.stories.jsx +7 -0
  96. package/src/Button/index.jsx +21 -0
  97. package/src/Connect/Connect.stories.jsx +34 -0
  98. package/src/Connect/assets/locale.js +145 -0
  99. package/src/Connect/assets/login-bg.png +0 -0
  100. package/src/Connect/assets/login-slogan.js +7 -0
  101. package/src/Connect/components/action-button.jsx +22 -0
  102. package/src/Connect/components/app-tips.jsx +156 -0
  103. package/src/Connect/components/auto-height.jsx +38 -0
  104. package/src/Connect/components/back-button.jsx +23 -0
  105. package/src/Connect/components/connect-status.jsx +259 -0
  106. package/src/Connect/components/did-connect-title.jsx +106 -0
  107. package/src/Connect/components/download-tips.jsx +55 -0
  108. package/src/Connect/components/loading.jsx +25 -0
  109. package/src/Connect/components/login-item/connect-choose-list.jsx +304 -0
  110. package/src/Connect/components/login-item/login-method-item.jsx +118 -0
  111. package/src/Connect/components/login-item/mobile-login-item.jsx +179 -0
  112. package/src/Connect/components/login-item/passkey-login-item.jsx +52 -0
  113. package/src/Connect/components/login-item/web-login-item.jsx +149 -0
  114. package/src/Connect/components/mask-overlay.jsx +32 -0
  115. package/src/Connect/components/refresh-overlay.jsx +52 -0
  116. package/src/Connect/components/switch-app.jsx +69 -0
  117. package/src/Connect/contexts/state.jsx +219 -0
  118. package/src/Connect/fullpage.jsx +3 -0
  119. package/src/Connect/hooks/auth-url.js +31 -0
  120. package/src/Connect/hooks/method-list.js +121 -0
  121. package/src/Connect/hooks/page-show.js +24 -0
  122. package/src/Connect/hooks/security.js +40 -0
  123. package/src/Connect/hooks/token.js +639 -0
  124. package/src/Connect/hooks/use-apps.js +69 -0
  125. package/src/Connect/hooks/use-quick-connect.js +130 -0
  126. package/src/Connect/index.jsx +600 -0
  127. package/src/Connect/landing-page.jsx +3 -0
  128. package/src/Connect/plugins/email/index.jsx +82 -0
  129. package/src/Connect/plugins/email/list-item.jsx +31 -0
  130. package/src/Connect/plugins/email/placeholder.jsx +365 -0
  131. package/src/Connect/plugins/index.js +2 -0
  132. package/src/Connect/use-connect.jsx +321 -0
  133. package/src/Connect/with-blocklet.jsx +26 -0
  134. package/src/Connect/with-bridge-call.jsx +138 -0
  135. package/src/Federated/context.jsx +93 -0
  136. package/src/Federated/index.jsx +1 -0
  137. package/src/Logo/index.jsx +2 -0
  138. package/src/OAuth/context.jsx +346 -0
  139. package/src/OAuth/guest.svg +20 -0
  140. package/src/OAuth/index.jsx +1 -0
  141. package/src/OAuth/passport-switcher.jsx +133 -0
  142. package/src/Passkey/actions.jsx +212 -0
  143. package/src/Passkey/constants.js +2 -0
  144. package/src/Passkey/context.jsx +381 -0
  145. package/src/Passkey/dialog.jsx +391 -0
  146. package/src/Passkey/icon.jsx +10 -0
  147. package/src/Passkey/index.jsx +2 -0
  148. package/src/Service/index.jsx +96 -0
  149. package/src/Session/assets/did-spaces-guide-cover.svg +128 -0
  150. package/src/Session/assets/did-spaces-guide-icon.svg +7 -0
  151. package/src/Session/context.jsx +7 -0
  152. package/src/Session/did-spaces-guide.jsx +173 -0
  153. package/src/Session/hooks/use-federated.js +88 -0
  154. package/src/Session/hooks/use-mobile.jsx +6 -0
  155. package/src/Session/hooks/use-protected-routes.js +16 -0
  156. package/src/Session/hooks/use-session-token.js +365 -0
  157. package/src/Session/hooks/use-verify.jsx +76 -0
  158. package/src/Session/index.jsx +1687 -0
  159. package/src/Session/libs/constants.js +14 -0
  160. package/src/Session/libs/did-spaces.js +38 -0
  161. package/src/Session/libs/federated.js +79 -0
  162. package/src/Session/libs/index.js +5 -0
  163. package/src/Session/libs/locales.js +160 -0
  164. package/src/Session/libs/login-mobile.js +80 -0
  165. package/src/Session/window-focus-aware.jsx +28 -0
  166. package/src/SessionManager/index.jsx +2 -0
  167. package/src/Storage/engine/cookie.js +23 -0
  168. package/src/Storage/engine/local-storage.js +55 -0
  169. package/src/Storage/index.js +25 -0
  170. package/src/User/index.js +4 -0
  171. package/src/User/use-did.js +80 -0
  172. package/src/User/wrap-did.jsx +18 -0
  173. package/src/WebWalletSWKeeper/index.jsx +3 -0
  174. package/src/constant.js +26 -0
  175. package/src/error.js +6 -0
  176. package/src/hooks/use-locale.jsx +6 -0
  177. package/src/index.js +43 -0
  178. package/src/locales/en.jsx +15 -0
  179. package/src/locales/index.jsx +13 -0
  180. package/src/locales/zh.jsx +15 -0
  181. package/src/types.d.ts +355 -0
  182. package/src/utils.js +395 -0
  183. package/vite.config.mjs +29 -0
@@ -0,0 +1,321 @@
1
+ import { createPortal } from 'react-dom';
2
+ import { useCreation, useMemoizedFn, useReactive, useDebounce } from 'ahooks';
3
+ import { use, useImperativeHandle, useRef, useState } from 'react';
4
+ import noop from 'lodash/noop';
5
+ import cloneDeep from 'lodash/cloneDeep';
6
+ import isFunction from 'lodash/isFunction';
7
+ import isUndefined from 'lodash/isUndefined';
8
+ import { joinURL, withQuery } from 'ufo';
9
+ import { getFederatedEnabled, getMaster } from '@arcblock/ux/lib/Util/federated';
10
+ import PQueue from 'p-queue';
11
+ import { useTheme } from '@arcblock/ux/lib/Theme';
12
+ import { getCookieOptions } from '@arcblock/ux/lib/Util';
13
+ import Cookie from 'js-cookie';
14
+ import { useBrowser } from '@arcblock/react-hooks';
15
+
16
+ import { SessionContext } from '../Session/context';
17
+ import { runPopup, openPopup, updateConnectedInfo, getAppId } from '../utils';
18
+ import { API_DID_PREFIX, BLOCKLET_SERVICE_PATH_PREFIX } from '../constant';
19
+ import DidConnect from './index';
20
+
21
+ const CLOSE_TIMEOUT = 2000;
22
+
23
+ // eslint-disable-next-line react/prop-types
24
+ function ConnectHolder({ ref }) {
25
+ const { api, session } = use(SessionContext);
26
+ const [targetMessages, setTargetMessages] = useState({
27
+ title: 'DID Connect',
28
+ scan: 'Use follwing methods to complete this action',
29
+ confirm: 'Confirm in your account',
30
+ success: 'Success!',
31
+ });
32
+ const theme = useTheme();
33
+ const defaultValue = {
34
+ containerEl: null,
35
+ popup: true,
36
+ checkFn: api?.get || noop,
37
+ };
38
+ const leavingScreenDelay = theme?.transitions?.duration?.leavingScreen || 500; // 默认值是 195
39
+ const state = useReactive({
40
+ show: false,
41
+ ...cloneDeep(defaultValue),
42
+ });
43
+ const debouncedShow = useDebounce(state.show, {
44
+ wait: leavingScreenDelay,
45
+ });
46
+
47
+ const [restParams, setRestParams] = useState({});
48
+
49
+ /**
50
+ * 重置 use-connect 中的数据
51
+ */
52
+ const reset = useMemoizedFn(() => {
53
+ const cloneValue = cloneDeep(defaultValue);
54
+ Object.keys(cloneValue).forEach((key) => {
55
+ state[key] = cloneValue[key];
56
+ });
57
+ });
58
+
59
+ /**
60
+ * 关闭 did-connect 连接
61
+ * @type {import('../types').CloseConnect}
62
+ */
63
+ const close = useMemoizedFn(() => {
64
+ state.show = false;
65
+ // HACK: 这里不能增加延时,否则会导致某些情况下,赋值的数据被 reset(比如页面中检测到退出后立即唤起登录功能)
66
+ setTimeout(() => {
67
+ reset();
68
+ }, leavingScreenDelay);
69
+ });
70
+
71
+ /**
72
+ * 唤起一个 did-connect 连接
73
+ * @type {import('../types').OpenConnect}
74
+ */
75
+ const open = useMemoizedFn((params) => {
76
+ const { containerEl, checkFn, popup, messages, ...rest } = cloneDeep(params);
77
+ if (typeof checkFn === 'function') state.checkFn = checkFn;
78
+ if (popup !== undefined) state.popup = popup;
79
+ if (messages !== undefined) setTargetMessages(messages);
80
+ if (containerEl !== undefined) state.containerEl = containerEl;
81
+
82
+ setRestParams(rest);
83
+ state.show = true;
84
+ });
85
+ const oauth = session.useOAuth();
86
+
87
+ const loginOAuth = useMemoizedFn(async ({ provider, action, extraParams, onLogin }) => {
88
+ const loginResult = await oauth.loginOAuth(
89
+ { provider },
90
+ {
91
+ action,
92
+ ...extraParams,
93
+ }
94
+ );
95
+ const cookieOptions = getCookieOptions({ returnDomain: false });
96
+ Cookie.remove('connected_did', cookieOptions);
97
+ Cookie.remove('connected_pk', cookieOptions);
98
+ Cookie.remove('connected_wallet_os', cookieOptions);
99
+ onLogin(
100
+ {
101
+ ...loginResult,
102
+ encrypted: false,
103
+ },
104
+ (val) => val
105
+ );
106
+ });
107
+
108
+ useImperativeHandle(ref, () => {
109
+ return {
110
+ open,
111
+ loginOAuth,
112
+ close,
113
+ };
114
+ }, [open, close, loginOAuth]);
115
+
116
+ const content = (
117
+ <DidConnect
118
+ open={state.show}
119
+ popup={state.popup}
120
+ messages={targetMessages}
121
+ checkFn={state.checkFn}
122
+ // 如果采用 state.restParams 的方式来传值,会导致 DidConnect 组件接收的值是旧的
123
+ {...restParams}
124
+ />
125
+ );
126
+
127
+ if (state.containerEl) {
128
+ return createPortal(content, state.containerEl);
129
+ }
130
+
131
+ if (state.show) {
132
+ return content;
133
+ }
134
+ if (debouncedShow) {
135
+ return content;
136
+ }
137
+ return null;
138
+ }
139
+
140
+ export default function useConnect() {
141
+ const connectRef = useRef(null);
142
+ const browser = useBrowser();
143
+ const queue = useCreation(() => {
144
+ const queueOptions = { concurrency: 1 };
145
+ // HACK: ArcSphere 中的 DID Connect 不需要排队,可以并发,交给客户端自己来处理
146
+ if (browser.arcSphere) {
147
+ delete queueOptions.concurrency;
148
+ }
149
+ return new PQueue(queueOptions);
150
+ });
151
+
152
+ /**
153
+ * 关闭 did-connect 连接
154
+ * @type {import('../types').CloseConnect}
155
+ */
156
+ const close = useMemoizedFn((...args) => {
157
+ connectRef.current?.close(...args);
158
+ });
159
+ /**
160
+ * 唤起一个 did-connect 连接
161
+ * @type {import('../types').OpenConnect}
162
+ */
163
+ const open = useMemoizedFn(
164
+ ({ onSuccess, onClose, onEnd, closeTimeout = CLOSE_TIMEOUT, ...restParams }, ...restargs) => {
165
+ const openFn = () => {
166
+ return new Promise((resolve) => {
167
+ if (typeof onSuccess === 'function') {
168
+ restParams.onSuccess = (...args) => {
169
+ onSuccess(...args);
170
+ setTimeout(() => {
171
+ close();
172
+ resolve();
173
+ }, closeTimeout);
174
+ };
175
+ } else {
176
+ restParams.onSuccess = () => {
177
+ setTimeout(() => {
178
+ close();
179
+ resolve();
180
+ }, closeTimeout);
181
+ };
182
+ }
183
+ if (typeof onClose === 'function') {
184
+ restParams.onClose = (...args) => {
185
+ onClose(...args);
186
+ close();
187
+ resolve();
188
+ queue.clear();
189
+ };
190
+ } else {
191
+ restParams.onClose = () => {
192
+ close();
193
+ resolve();
194
+ queue.clear();
195
+ };
196
+ }
197
+ // HACK: 需要让 react 先清理掉旧的 DidConnect 组件实例,再打开一个新的,否则会造成 did-connect 中的 state 未被重新初始化
198
+ setTimeout(() => {
199
+ connectRef.current?.open(restParams, ...restargs);
200
+ // FIXME: @zhanghan 弄清楚需要延迟的原因,目前不设置 setTimeout 延时会造成 state 未被正确赋值
201
+ }, 300);
202
+ });
203
+ };
204
+ queue.add(openFn);
205
+ }
206
+ );
207
+
208
+ /**
209
+ * @type {import('../types').OpenConnectPopup}
210
+ */
211
+ const _openPopup = useMemoizedFn(
212
+ (
213
+ _params = {},
214
+ { baseUrl, locale = 'en', blocklet = globalThis?.blocklet, closeTimeout = CLOSE_TIMEOUT, popupOptions } = {}
215
+ ) => {
216
+ return new Promise((resolveRoot, rejectRoot) => {
217
+ const openPopupFn = () => {
218
+ // eslint-disable-next-line no-async-promise-executor
219
+ return new Promise((resolveChild) => {
220
+ const innerFn = async () => {
221
+ if (isUndefined(baseUrl)) {
222
+ const federatedEnabled = getFederatedEnabled();
223
+ const masterSite = getMaster();
224
+ // eslint-disable-next-line no-param-reassign
225
+ baseUrl = federatedEnabled ? masterSite.appUrl : window.location.origin;
226
+ }
227
+ const { onSuccess, onClose, onError, custom = false, ...params } = _params;
228
+ if (!params?.extraParams?.provider) {
229
+ if (isFunction(onError)) {
230
+ onError('Must have extraParams.provider');
231
+ }
232
+ throw new Error('Must have extraParams.provider');
233
+ }
234
+ if (!params?.prefix) {
235
+ params.prefix = window.location.origin + joinURL(blocklet?.prefix, API_DID_PREFIX);
236
+ }
237
+
238
+ const popupUrlParams = {
239
+ params: btoa(encodeURIComponent(JSON.stringify(params))),
240
+ appPid: blocklet?.appPid,
241
+ locale,
242
+ disableClose: true, // 弹窗模式默认不允许关闭 DID Connect
243
+ };
244
+
245
+ const popup = openPopup(
246
+ custom
247
+ ? withQuery(joinURL(baseUrl, params.prefix), popupUrlParams)
248
+ : withQuery(joinURL(baseUrl || '/', BLOCKLET_SERVICE_PATH_PREFIX, '/connect'), popupUrlParams),
249
+ popupOptions
250
+ );
251
+
252
+ try {
253
+ const data = await runPopup({ popup, closeTimeout });
254
+
255
+ updateConnectedInfo(
256
+ {
257
+ connected_app: getAppId(),
258
+ ...data?.options,
259
+ },
260
+ true
261
+ );
262
+
263
+ if (isFunction(onSuccess)) {
264
+ onSuccess({ ...data.response, encrypted: false });
265
+ close();
266
+ resolveChild();
267
+ } else {
268
+ close();
269
+ resolveChild();
270
+ }
271
+ resolveRoot({ ...data.response, encrypted: false });
272
+ } catch (err) {
273
+ console.error(err);
274
+ if (err.message === 'Popup closed') {
275
+ if (isFunction(onClose)) {
276
+ onClose();
277
+ resolveChild();
278
+ queue.clear();
279
+ } else {
280
+ resolveChild();
281
+ queue.clear();
282
+ }
283
+ } else if (isFunction(onError)) {
284
+ onError(err);
285
+ }
286
+ throw err;
287
+ }
288
+ };
289
+ innerFn().catch((error) => {
290
+ resolveChild();
291
+ rejectRoot(error);
292
+ });
293
+ });
294
+ };
295
+ queue.add(openPopupFn);
296
+ });
297
+ }
298
+ );
299
+
300
+ const loginOAuth = (options) => {
301
+ connectRef.current.loginOAuth(options);
302
+ };
303
+
304
+ const connectHolder = useCreation(() => {
305
+ return <ConnectHolder ref={connectRef} />;
306
+ });
307
+ const connectApi = useCreation(() => {
308
+ return {
309
+ open,
310
+ close,
311
+ loginOAuth,
312
+ openPopup: _openPopup,
313
+ queue,
314
+ };
315
+ });
316
+
317
+ return {
318
+ connectHolder,
319
+ connectApi,
320
+ };
321
+ }
@@ -0,0 +1,26 @@
1
+ /* eslint-disable react/prop-types */
2
+ import { BlockletProvider, useBlockletContext } from '@arcblock/ux/lib/BlockletContext';
3
+
4
+ export default function withBlocklet(Component) {
5
+ // HACK: 该组件是为了在 baseUrl 设置为其他 blocklet 的 appUrl 时,能够正确读取到目标的 blocklet
6
+ // 比如在 A blocklet 中,添加了一个 B blocklet 的 did-connect,此时 did-connect 的数据要以 B blocklet 为数据基础
7
+ function ComponentWrap({ ...props }) {
8
+ const { blocklet, masterBlocklet } = useBlockletContext();
9
+
10
+ return <Component {...props} blocklet={blocklet} masterBlocklet={masterBlocklet} />;
11
+ }
12
+
13
+ /**
14
+ * @param {{baseUrl: string}} props
15
+ * @returns
16
+ */
17
+ function WithProviderComponent({ baseUrl, ...rest }) {
18
+ return (
19
+ <BlockletProvider baseUrl={baseUrl}>
20
+ <ComponentWrap baseUrl={baseUrl} {...rest} />
21
+ </BlockletProvider>
22
+ );
23
+ }
24
+
25
+ return WithProviderComponent;
26
+ }
@@ -0,0 +1,138 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import { use, useEffect } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import bridge from '@arcblock/bridge';
5
+ import { useDebounceFn, useMemoizedFn } from 'ahooks';
6
+ import pick from 'lodash/pick';
7
+ import useBrowser from '@arcblock/react-hooks/lib/useBrowser';
8
+ import { isUrl } from '@arcblock/ux/lib/Util';
9
+ import { joinURL } from 'ufo';
10
+
11
+ import { debug } from '../utils';
12
+ import { API_DID_PREFIX, BLOCKLET_SERVICE_PATH_PREFIX, DEFAULT_TIMEOUT } from '../constant';
13
+ import useSecurity from './hooks/security';
14
+ import { SessionContext } from '../Session/context';
15
+ import { getWalletDid } from '../User/use-did';
16
+
17
+ export default function withBridgeCall(Component) {
18
+ const defaultConnectParams = {
19
+ prefix: API_DID_PREFIX,
20
+ checkInterval: 2 * 1000,
21
+ checkTimeout: DEFAULT_TIMEOUT * 1000,
22
+ locale: 'en',
23
+ tokenKey: '_t_',
24
+ encKey: '_ek_',
25
+ baseUrl: '',
26
+ messages: {},
27
+ extraParams: {},
28
+ forceConnected: true,
29
+ enabledConnectTypes: [], // 所有的
30
+ disableSwitchApp: false,
31
+ hideCloseButton: false,
32
+ qrcodeSize: 160,
33
+ options: {},
34
+ };
35
+ function WithBridgeCallComponent(props) {
36
+ const { encryptKey, decrypt } = useSecurity();
37
+ const browser = useBrowser();
38
+ const sessionContext = use(SessionContext);
39
+ const currentUserDid = getWalletDid(sessionContext?.session?.user);
40
+ const params = pick(props, [
41
+ //
42
+ 'action',
43
+ 'checkTimeout',
44
+ 'locale',
45
+ 'messages',
46
+ // 'webWalletUrl', arcSphere 中不需要通过 web 登录,不需要此参数
47
+ //
48
+ 'baseUrl', // 要写清楚 baseUrl 和 prefix 的处理逻辑
49
+ 'prefix',
50
+ 'checkInterval',
51
+ 'closeTimeout',
52
+ 'extraParams',
53
+ 'tokenKey',
54
+ 'encKey',
55
+ 'forceConnected',
56
+ 'provider',
57
+ 'enabledConnectTypes',
58
+ 'disableSwitchApp',
59
+ 'hideCloseButton', // 不允许用户关闭这个 connect
60
+ // 'qrcodeSize', // 不应该允许配置?
61
+ 'options', // FIXME: @zhanghan 弄清楚参数的字段
62
+ //
63
+ // 'autoConnect',
64
+ // 'saveConnect',
65
+ // 'useSocket', // 后期再考虑实现
66
+ // 'allowWallet',
67
+ // 'showDownload',
68
+
69
+ // 函数,需要在这一层进行处理
70
+ // 'onError',
71
+ // 'onSuccess',
72
+ // 'onRecreateSession', // 貌似没有地方用,直接去掉?
73
+ // 'onClose',
74
+ ]);
75
+ const mergedParams = Object.assign({}, defaultConnectParams, params);
76
+ if (mergedParams.forceConnected === true) {
77
+ mergedParams.forceConnected = currentUserDid || true;
78
+ }
79
+ // NOTICE: 传递给 ArcSphere 的 prefix 需要妥善处理 serviceHost 的拼接
80
+ // Web 端的 DID Connect serviceHost 会作为 api 实例的 baseUrl,所以不需要拼接在 prefix 中
81
+ // eslint-disable-next-line react/prop-types
82
+ const serviceHost = props.serviceHost || window?.env?.apiPrefix || window?.blocklet?.prefix || '/';
83
+ if (!isUrl(mergedParams.prefix) && !mergedParams.prefix?.startsWith(BLOCKLET_SERVICE_PATH_PREFIX)) {
84
+ mergedParams.prefix = joinURL(serviceHost, mergedParams.prefix);
85
+ }
86
+
87
+ const showOpenArcSphereDIDConnect = browser.arcSphere && props.open && !window.forceOpenConnect;
88
+
89
+ const { run: callClientDidConnect } = useDebounceFn(
90
+ useMemoizedFn(async () => {
91
+ debug('bridge call arcDidConnect', props, params, mergedParams);
92
+ try {
93
+ const result = await bridge.callArc('didConnect', { ...mergedParams, [mergedParams.encKey]: encryptKey });
94
+ debug('bridge callArc didConnect: result', result);
95
+ if (result.action === 'error') {
96
+ // eslint-disable-next-line react/prop-types
97
+ props.onError(result.data);
98
+ return;
99
+ }
100
+ if (result.action === 'success') {
101
+ // eslint-disable-next-line react/prop-types
102
+ props.onSuccess(result.data, decrypt);
103
+ return;
104
+ }
105
+ // close | cancel
106
+ // eslint-disable-next-line react/prop-types
107
+ props.onClose(result.data);
108
+ // action 不匹配的时候,什么都不做
109
+ } catch (err) {
110
+ logger.error('Failed to callArc didConnect', err);
111
+ }
112
+ }),
113
+ {
114
+ leading: true,
115
+ wait: 300,
116
+ }
117
+ );
118
+
119
+ useEffect(() => {
120
+ if (showOpenArcSphereDIDConnect) {
121
+ debug('WrapConnect call');
122
+ callClientDidConnect();
123
+ }
124
+ }, [callClientDidConnect, showOpenArcSphereDIDConnect]);
125
+
126
+ if (showOpenArcSphereDIDConnect) {
127
+ return null;
128
+ }
129
+
130
+ return <Component {...props} />;
131
+ }
132
+
133
+ WithBridgeCallComponent.propTypes = {
134
+ open: PropTypes.bool,
135
+ };
136
+
137
+ return WithBridgeCallComponent;
138
+ }
@@ -0,0 +1,93 @@
1
+ import { createContext, use, useRef, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import omit from 'lodash/omit';
4
+ import { RequestStorageAccessApiDialog } from '@arcblock/ux/lib/DIDConnect';
5
+ import { joinURL } from 'ufo';
6
+ import { getFederatedEnabled, getMaster } from '@arcblock/ux/lib/Util/federated';
7
+ import { useCreation, useMemoizedFn, useLocalStorageState, useInterval } from 'ahooks';
8
+ import { BLOCKLET_SERVICE_PATH_PREFIX } from '@arcblock/ux/lib/Util/constant';
9
+ import Cookie from 'js-cookie';
10
+
11
+ import { getBrowserLang } from '../utils';
12
+ import { CHECK_INTERVAL_TIME, LANG_COOKIE_NAME } from '../constant';
13
+
14
+ const FederatedContext = createContext({});
15
+ const { Provider, Consumer: FederatedConsumer } = FederatedContext;
16
+
17
+ function FederatedProvider({ children, locale: initialLocale = 'en' }) {
18
+ const blocklet = window?.blocklet;
19
+ const federatedEnabled = getFederatedEnabled(blocklet);
20
+ const masterSite = getMaster(blocklet);
21
+ const requestStorageAccessApiDialogRef = useRef(null);
22
+ const [storageAccess, setStorageAccess] = useLocalStorageState('storage-access', {
23
+ defaultValue: {
24
+ appUrl: masterSite?.appUrl,
25
+ iat: Date.now(),
26
+ state: undefined,
27
+ },
28
+ });
29
+ const [locale, setLocale] = useState(initialLocale);
30
+
31
+ const checkCookieLocale = useMemoizedFn(() => {
32
+ const latestLocale = Cookie.get(LANG_COOKIE_NAME) || getBrowserLang();
33
+ if (latestLocale !== locale) {
34
+ setLocale(latestLocale);
35
+ }
36
+ });
37
+
38
+ const requestStorageAccess = useMemoizedFn(async () => {
39
+ const { value: hasAccess, origin } = await requestStorageAccessApiDialogRef.current.requestStorageAccess();
40
+ let state = storageAccess.state || 'prompt';
41
+ if (origin === 'broswer') {
42
+ if (storageAccess.state) state = hasAccess ? 'granted' : 'denied';
43
+ }
44
+
45
+ // NOTICE: 只有变化时才记录,确保 iat 值是相对可信的
46
+ if (state !== storageAccess.state) {
47
+ setStorageAccess({
48
+ ...storageAccess,
49
+ iat: Date.now(),
50
+ state,
51
+ });
52
+ }
53
+ return hasAccess;
54
+ });
55
+
56
+ const bridgeContent = useCreation(() => {
57
+ if (masterSite?.appUrl && federatedEnabled) {
58
+ return (
59
+ <RequestStorageAccessApiDialog
60
+ ref={requestStorageAccessApiDialogRef}
61
+ locale={locale}
62
+ src={joinURL(masterSite.appUrl, BLOCKLET_SERVICE_PATH_PREFIX, '/share/shared-bridge.html')}
63
+ storageAccessState={storageAccess.state}
64
+ />
65
+ );
66
+ }
67
+ return null;
68
+ }, [locale, masterSite?.appUrl, federatedEnabled, storageAccess.appUrl, storageAccess.iat, storageAccess.state]);
69
+
70
+ useInterval(() => {
71
+ checkCookieLocale();
72
+ }, CHECK_INTERVAL_TIME);
73
+
74
+ return (
75
+ <Provider value={{ requestStorageAccess }}>
76
+ {children}
77
+ {bridgeContent}
78
+ </Provider>
79
+ );
80
+ }
81
+
82
+ FederatedProvider.propTypes = {
83
+ children: PropTypes.node.isRequired,
84
+ locale: PropTypes.string,
85
+ };
86
+
87
+ function useFederatedContext() {
88
+ const context = use(FederatedContext);
89
+
90
+ return omit(context, ['locale']);
91
+ }
92
+
93
+ export { FederatedContext, FederatedConsumer, FederatedProvider, useFederatedContext };
@@ -0,0 +1 @@
1
+ export * from './context';
@@ -0,0 +1,2 @@
1
+ // eslint-disable-next-line no-restricted-exports
2
+ export { default } from '@arcblock/ux/lib/DIDLogo';