@blocklet/did-domain-react 0.3.39

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 (56) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +1 -0
  3. package/babel.config.es.js +8 -0
  4. package/build.config.ts +29 -0
  5. package/env.d.ts +17 -0
  6. package/es/buy.d.ts +19 -0
  7. package/es/buy.js +124 -0
  8. package/es/domain.d.ts +11 -0
  9. package/es/domain.js +73 -0
  10. package/es/index.d.ts +4 -0
  11. package/es/index.js +4 -0
  12. package/es/libs/api.d.ts +3 -0
  13. package/es/libs/api.js +21 -0
  14. package/es/libs/util.d.ts +21 -0
  15. package/es/libs/util.js +73 -0
  16. package/es/locales/en.d.ts +2 -0
  17. package/es/locales/en.js +15 -0
  18. package/es/locales/index.d.ts +4 -0
  19. package/es/locales/index.js +6 -0
  20. package/es/locales/zh.d.ts +2 -0
  21. package/es/locales/zh.js +15 -0
  22. package/es/types/index.d.ts +0 -0
  23. package/es/types/index.js +0 -0
  24. package/es/types/shims.d.ts +15 -0
  25. package/global.d.ts +54 -0
  26. package/lib/buy.d.ts +19 -0
  27. package/lib/buy.js +177 -0
  28. package/lib/domain.d.ts +11 -0
  29. package/lib/domain.js +80 -0
  30. package/lib/index.d.ts +4 -0
  31. package/lib/index.js +27 -0
  32. package/lib/libs/api.d.ts +3 -0
  33. package/lib/libs/api.js +25 -0
  34. package/lib/libs/util.d.ts +21 -0
  35. package/lib/libs/util.js +83 -0
  36. package/lib/locales/en.d.ts +2 -0
  37. package/lib/locales/en.js +22 -0
  38. package/lib/locales/index.d.ts +4 -0
  39. package/lib/locales/index.js +13 -0
  40. package/lib/locales/zh.d.ts +2 -0
  41. package/lib/locales/zh.js +22 -0
  42. package/lib/types/index.d.ts +0 -0
  43. package/lib/types/index.js +1 -0
  44. package/lib/types/shims.d.ts +15 -0
  45. package/package.json +106 -0
  46. package/src/buy.tsx +154 -0
  47. package/src/domain.tsx +77 -0
  48. package/src/index.ts +6 -0
  49. package/src/libs/api.ts +28 -0
  50. package/src/libs/util.ts +100 -0
  51. package/src/locales/en.ts +17 -0
  52. package/src/locales/index.ts +7 -0
  53. package/src/locales/zh.ts +16 -0
  54. package/src/types/index.ts +0 -0
  55. package/src/types/shims.d.ts +15 -0
  56. package/vite.config.ts +6 -0
package/src/buy.tsx ADDED
@@ -0,0 +1,154 @@
1
+ import Button from '@arcblock/ux/lib/Button';
2
+ import Dialog from '@arcblock/ux/lib/Dialog';
3
+ import { LocaleProvider, useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import type { Locale } from '@arcblock/ux/lib/type';
5
+ import CheckCircle from '@mui/icons-material/CheckCircle';
6
+ import ShoppingCart from '@mui/icons-material/ShoppingCart';
7
+ import type { ButtonProps } from '@mui/material';
8
+ import Alert from '@mui/material/Alert';
9
+ import Box from '@mui/material/Box';
10
+ import CircularProgress from '@mui/material/CircularProgress';
11
+ import Typography from '@mui/material/Typography';
12
+ import { useRequest as useAHooksRequest, useMemoizedFn, useSetState, useUnmount } from 'ahooks';
13
+ import { useRef } from 'react';
14
+ import { joinURL } from 'ufo';
15
+
16
+ import { create } from './libs/api';
17
+ import { openPopup } from './libs/util';
18
+ import { translations } from './locales';
19
+
20
+ enum BuyState {
21
+ Prepare = 1,
22
+ InProgress = 2,
23
+ Success = 4,
24
+ Completed = 8,
25
+ }
26
+
27
+ interface Props {
28
+ delegatee: string;
29
+ delegateePk: string;
30
+ didDomainURL: string;
31
+ locale: Locale;
32
+ onSuccess?: (data: { nftDid: string; domain: string; chainHost: string }) => void;
33
+ }
34
+
35
+ function Component({ delegatee, delegateePk, didDomainURL, locale, onSuccess, ...props }: Props & ButtonProps) {
36
+ const { t } = useLocaleContext();
37
+ const [state, setState] = useSetState<{ currentState: BuyState }>({
38
+ currentState: BuyState.Prepare,
39
+ });
40
+ const popup = useRef<Window | null>(null);
41
+ const domainURLObj = new URL(didDomainURL);
42
+ domainURLObj.searchParams.set('locale', locale);
43
+
44
+ const api = useMemoizedFn(() => create({ baseURL: joinURL(domainURLObj.origin, domainURLObj.pathname, 'api') }))();
45
+
46
+ const onMessage = useMemoizedFn((event: MessageEvent) => {
47
+ if (event.data.type === 'didDomain.success') {
48
+ onSuccess?.({ nftDid: event.data?.nftDid, domain: event.data?.domain, chainHost: event.data?.chainHost });
49
+ popup.current?.close();
50
+ popup.current = null;
51
+ setState({ currentState: BuyState.Success });
52
+
53
+ setTimeout(() => {
54
+ setState({ currentState: BuyState.Completed });
55
+ }, 2000);
56
+ }
57
+ });
58
+
59
+ useUnmount(() => {
60
+ try {
61
+ window.removeEventListener('message', onMessage);
62
+ popup.current?.close();
63
+ popup.current = null;
64
+ } catch (error) {
65
+ console.warn('Failed to close popup', error);
66
+ }
67
+ });
68
+
69
+ const handleCloseDialog = useMemoizedFn(() => {
70
+ setState({ currentState: BuyState.Prepare });
71
+ });
72
+
73
+ const handleOpenDialog = useMemoizedFn(() => {
74
+ setState({ currentState: BuyState.InProgress });
75
+ sessionRequest.run();
76
+ });
77
+
78
+ const handleCreatedSession = useMemoizedFn(({ sessionId }: { sessionId: string }) => {
79
+ window.addEventListener('message', onMessage);
80
+ domainURLObj.searchParams.set('sessionId', sessionId);
81
+ popup.current = openPopup(domainURLObj.toString());
82
+
83
+ const timer = setInterval(() => {
84
+ if (popup.current?.closed) {
85
+ clearInterval(timer);
86
+ handleCloseDialog();
87
+ }
88
+ }, 1000);
89
+
90
+ return () => clearInterval(timer);
91
+ });
92
+
93
+ const sessionRequest = useAHooksRequest(
94
+ async () => {
95
+ const { data } = await api.post('/payment/session', {
96
+ delegatee,
97
+ delegateePk,
98
+ });
99
+ return handleCreatedSession({ sessionId: data.sessionId });
100
+ },
101
+ {
102
+ manual: true,
103
+ debounceWait: 500,
104
+ debounceLeading: true,
105
+ debounceTrailing: false,
106
+ debounceMaxWait: 3000,
107
+ refreshDeps: [],
108
+ onError(error: Error) {
109
+ console.error(error);
110
+ },
111
+ }
112
+ );
113
+
114
+ return (
115
+ <>
116
+ <Button onClick={handleOpenDialog} variant="contained" startIcon={<ShoppingCart />} {...props}>
117
+ {t('buy.button.title')}
118
+ </Button>
119
+ <Dialog
120
+ fullWidth
121
+ maxWidth="xs"
122
+ open={state.currentState > BuyState.Prepare && state.currentState < BuyState.Completed}
123
+ onClose={handleCloseDialog}>
124
+ {!sessionRequest.error && (
125
+ <Box sx={{ display: 'flex', alignItems: 'center', flexDirection: 'column', gap: 2 }}>
126
+ {state.currentState === BuyState.InProgress && <CircularProgress size={50} />}
127
+ {state.currentState === BuyState.Success && (
128
+ <CheckCircle sx={{ color: 'primary.main', fontSize: '5rem' }} />
129
+ )}
130
+ {state.currentState === BuyState.InProgress && <Typography>{t('buy.status.inProgress')}</Typography>}
131
+ {state.currentState === BuyState.Success && <Typography>{t('buy.status.success')}</Typography>}
132
+ </Box>
133
+ )}
134
+ {sessionRequest.error && <Alert severity="error">{t('buy.error.failedToCreateSession')}</Alert>}
135
+ </Dialog>
136
+ </>
137
+ );
138
+ }
139
+
140
+ Component.defaultProps = {
141
+ onSuccess: () => {},
142
+ };
143
+
144
+ export default function Buy(props: Props) {
145
+ return (
146
+ <LocaleProvider locale={props.locale} translations={translations}>
147
+ <Component {...props} />
148
+ </LocaleProvider>
149
+ );
150
+ }
151
+
152
+ Buy.defaultProps = {
153
+ onSuccess: () => {},
154
+ };
package/src/domain.tsx ADDED
@@ -0,0 +1,77 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import Box from '@mui/material/Box';
3
+ import Popover from '@mui/material/Popover';
4
+ import { useTheme } from '@mui/material/styles';
5
+ import useMediaQuery from '@mui/material/useMediaQuery';
6
+ import { useRef } from 'react';
7
+ import useSetState from 'react-use/lib/useSetState';
8
+ import { joinURL } from 'ufo';
9
+
10
+ import { mergeSx } from './libs/util';
11
+
12
+ export function Domain({
13
+ nftDid,
14
+ didDomainURL,
15
+ locale,
16
+ sx,
17
+ }: {
18
+ nftDid: string;
19
+ didDomainURL: string;
20
+ locale: string;
21
+ sx?: any;
22
+ }) {
23
+ const { t } = useLocaleContext();
24
+ const [state, setState] = useSetState({
25
+ safeIframeRef: null,
26
+ popoverAnchorEl: null,
27
+ });
28
+ const iframeRef = useRef(null);
29
+ const theme = useTheme();
30
+ const isMobile = useMediaQuery(theme.breakpoints.down('md'));
31
+
32
+ const handlePopoverClick = (event: any) => {
33
+ setState({ popoverAnchorEl: event.currentTarget });
34
+ };
35
+
36
+ const handlePopoverClose = () => {
37
+ setState({ popoverAnchorEl: null });
38
+ };
39
+
40
+ const iframeSrc = joinURL(didDomainURL, `/app/embed/domain?nft-did=${nftDid}&locale=${locale}`);
41
+
42
+ return (
43
+ <>
44
+ <Box
45
+ component="img"
46
+ src={joinURL(didDomainURL, '/.well-known/service/blocklet/logo')}
47
+ alt="logo"
48
+ width={16}
49
+ height={16}
50
+ onClick={handlePopoverClick}
51
+ sx={mergeSx({ cursor: 'pointer' }, sx)}
52
+ />
53
+ <Popover
54
+ id="popover-info"
55
+ open={Boolean(state.popoverAnchorEl)}
56
+ anchorEl={state.popoverAnchorEl}
57
+ onClose={handlePopoverClose}
58
+ anchorOrigin={{
59
+ vertical: 'bottom',
60
+ horizontal: 'right',
61
+ }}>
62
+ <iframe
63
+ ref={iframeRef}
64
+ title={t('common.subscription')}
65
+ width={isMobile ? '320px' : '400px'}
66
+ height={isMobile ? '600px' : '680px'}
67
+ style={{ border: 0 }}
68
+ src={iframeSrc}
69
+ />
70
+ </Popover>
71
+ </>
72
+ );
73
+ }
74
+
75
+ Domain.defaultProps = {
76
+ sx: {},
77
+ };
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ import Buy from './buy';
2
+ import { Domain } from './domain';
3
+
4
+ export { translations } from './locales';
5
+
6
+ export { Buy, Domain };
@@ -0,0 +1,28 @@
1
+ import { getLocale } from '@arcblock/ux/lib/Locale/context';
2
+ import { createAxios } from '@blocklet/js-sdk';
3
+ import { joinURL } from 'ufo';
4
+
5
+ const axios = createAxios({});
6
+
7
+ axios.interceptors.request.use(
8
+ (config) => {
9
+ const prefix = window.blocklet ? window.blocklet.prefix : '/';
10
+ config.baseURL = prefix || '';
11
+ config.url = joinURL('/api', String(config.url));
12
+ config.timeout = 200000;
13
+
14
+ config.params = config.params || {};
15
+
16
+ const searchParams = new URLSearchParams(config.url.split('?')[1]);
17
+ if (!searchParams.has('locale')) {
18
+ config.params.locale = getLocale();
19
+ }
20
+
21
+ return config;
22
+ },
23
+ (error) => Promise.reject(error)
24
+ );
25
+
26
+ export const create = (...args: any) => createAxios(...args);
27
+
28
+ export default axios;
@@ -0,0 +1,100 @@
1
+ import { withQuery } from 'ufo';
2
+
3
+ export const SEARCH_BUTTON_WIDTH = 120;
4
+
5
+ export const COMMON_REQUEST_OPTIONS = {
6
+ debounceWait: 500,
7
+ debounceLeading: true,
8
+ debounceTrailing: false,
9
+ debounceMaxWait: 3000,
10
+ };
11
+
12
+ export const formatLocale = (locale = 'en') => {
13
+ if (locale === 'tw') {
14
+ return 'zh';
15
+ }
16
+
17
+ return locale;
18
+ };
19
+
20
+ export const formatError = (err: any, t: any) => {
21
+ const { errors, response } = err;
22
+
23
+ // graphql error
24
+ if (Array.isArray(errors)) {
25
+ return errors.map((x) => x.message).join('\n');
26
+ }
27
+
28
+ // joi validate error
29
+ const joiError = err.response?.data?.error;
30
+ if (Array.isArray(joiError?.details)) {
31
+ const formatted = joiError?.details.map((e: any) => {
32
+ const errorMessage = e.message.replace(/["]/g, "'");
33
+ const errorPath = e.path.join('.');
34
+ return `${errorPath}: ${errorMessage}`;
35
+ });
36
+
37
+ return `Validate failed: ${formatted.join(';')}`;
38
+ }
39
+
40
+ if (response?.data?.code) {
41
+ return t(`error.${response?.data?.code}`);
42
+ }
43
+
44
+ if (response?.data?.message) {
45
+ return response.data.message;
46
+ }
47
+
48
+ // axios error
49
+ if (response) {
50
+ return `Request failed: ${response.status} ${response.statusText}: ${JSON.stringify(response.data)}`;
51
+ }
52
+
53
+ return err.message;
54
+ };
55
+
56
+ export const mergeSx = (initial: any, sx: any) => {
57
+ if (!sx) {
58
+ return initial;
59
+ }
60
+
61
+ if (!initial) {
62
+ return sx;
63
+ }
64
+
65
+ return [initial, ...(Array.isArray(sx) ? sx : [sx])];
66
+ };
67
+
68
+ export const getChainHost = () => window.blocklet?.preferences?.chainHost || '';
69
+
70
+ /**
71
+ *
72
+ * @param {string} url - 打开一个弹窗
73
+ * @returns
74
+ */
75
+ export const openPopup = (url: string, { width = 600, height = 700, name = 'did-domain:popup' } = {}) => {
76
+ const left = window.screenX + (window.innerWidth - width) / 2;
77
+ const top = window.screenY + (window.innerHeight - height) / 2;
78
+
79
+ const windowFeatures = [
80
+ `left=${left}`,
81
+ `top=${top}`,
82
+ `width=${width}`,
83
+ `height=${height}`,
84
+ 'resizable=no', // not working
85
+ 'scrollbars=yes',
86
+ 'status=yes',
87
+ 'popup=yes',
88
+ ];
89
+
90
+ // HACK: IOS 必须以这种形式打开,才能被识别为 dialog 模式
91
+ const popup = window.open('', name, windowFeatures.join(','));
92
+ if (popup === null) {
93
+ throw new Error('Failed to open popup');
94
+ }
95
+ popup.location.href = withQuery(url, {
96
+ // NOTICE: 携带当前页面的 origin,用于在 popup 中通过该参数判断是否可以发送 postMessage,即使该参数被伪造,最终也只有该域名能接收到消息,所以没有关系
97
+ opener: window.location.origin,
98
+ });
99
+ return popup;
100
+ };
@@ -0,0 +1,17 @@
1
+ import flat from 'flat';
2
+
3
+ export default flat({
4
+ buy: {
5
+ button: {
6
+ title: 'Buy DID Domain',
7
+ },
8
+ status: {
9
+ inProgress:
10
+ 'Please wait while we are processing your request, the domain will be automatically resolved to the current app after the purchase...',
11
+ success: 'Purchase completed!',
12
+ },
13
+ error: {
14
+ failedToCreateSession: 'Failed to create session',
15
+ },
16
+ },
17
+ });
@@ -0,0 +1,7 @@
1
+ import en from './en';
2
+ import zh from './zh';
3
+
4
+ export const translations = {
5
+ zh,
6
+ en,
7
+ };
@@ -0,0 +1,16 @@
1
+ import flat from 'flat';
2
+
3
+ export default flat({
4
+ buy: {
5
+ button: {
6
+ title: '购买 DID 域名',
7
+ },
8
+ status: {
9
+ inProgress: '请稍等,我们正在处理您的请求,购买域名后会自动解析到当前的应用...',
10
+ success: '购买完成!',
11
+ },
12
+ error: {
13
+ failedToCreateSession: '创建会话失败',
14
+ },
15
+ },
16
+ });
File without changes
@@ -0,0 +1,15 @@
1
+ declare module '@arcblock/ux/*';
2
+
3
+ declare module '@arcblock/did-connect/*';
4
+
5
+ declare module '*.png';
6
+
7
+ declare module 'flat';
8
+
9
+ declare module '@arcblock/*';
10
+
11
+ declare module '@blocklet/*';
12
+
13
+ declare module 'pretty-ms-i18n';
14
+
15
+ declare var blocklet: import('@blocklet/sdk').WindowBlocklet;
package/vite.config.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'vite'; // eslint-disable-line
2
+ import { nodePolyfills } from 'vite-plugin-node-polyfills'; // eslint-disable-line
3
+
4
+ export default defineConfig({
5
+ plugins: [nodePolyfills({ protocolImports: true })],
6
+ });