@blocklet/did-space-react 0.5.65 → 0.5.67

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.
@@ -2,13 +2,13 @@
2
2
 
3
3
  const jsxRuntime = require('react/jsx-runtime');
4
4
  const React = require('react');
5
- const did = require('@arcblock/did');
6
5
  const material = require('@mui/material');
7
6
  const HelpOutlineIcon = require('@mui/icons-material/HelpOutline');
8
7
  const Button = require('@arcblock/ux/lib/Button');
9
8
  const SplitButton = require('@arcblock/ux/lib/SplitButton');
10
9
  const Dialog = require('@arcblock/ux/lib/Dialog');
11
10
  const ufo = require('ufo');
11
+ const axios = require('axios');
12
12
  const constants = require('../../libs/constants.js');
13
13
  const util = require('../../libs/util.js');
14
14
  const gateway = require('../../libs/gateway.js');
@@ -25,16 +25,17 @@ function BaseConnectTo({ style, onWalletClick, onGatewayConfirm, ...rest }) {
25
25
  setLoading(true);
26
26
  const spaceGatewayUrl = await gateway.getSpaceGatewayUrl(url);
27
27
  const didSpacesCoreUrl = util.extraDIDSpacesCoreUrl(spaceGatewayUrl);
28
- const spaceDid = util.getSpaceDidFromGatewayUrl(url);
29
- if (!did.isValid(spaceDid) || !await gateway.isValidSpaceGatewayUrl(didSpacesCoreUrl)) {
30
- throw new Error(t("storage.spaces.gateway.add.invalidUrl"));
31
- }
28
+ const spaceDid = util.getSpaceDidFromSpaceUrl(url);
29
+ await gateway.verifySpaceUrl({ spaceGatewayUrl, spaceDid, locale });
32
30
  onGatewayConfirm?.({
33
31
  spaceDid,
34
32
  spaceGatewayUrl: didSpacesCoreUrl
35
33
  });
36
34
  setOpen(false);
37
35
  } catch (err) {
36
+ if (err instanceof axios.AxiosError && util.isCorsBlockedError(err)) {
37
+ err.message = t("storage.spaces.error.corsBlocked");
38
+ }
38
39
  console.error(err);
39
40
  setErrorMessage(err.message);
40
41
  } finally {
@@ -3,6 +3,7 @@ import { SpaceGateway, SpaceStatus } from '../../types';
3
3
  export type Action = React.ReactNode | ((props: {
4
4
  spaceGateway: SpaceGateway;
5
5
  spaceStatus: SpaceStatus;
6
+ errorCode: number;
6
7
  selected: boolean;
7
8
  compat: boolean;
8
9
  refresh: () => void;
@@ -9,6 +9,8 @@ const DidAddress = require('@arcblock/ux/lib/DID');
9
9
  const Theme = require('@arcblock/ux/lib/Theme');
10
10
  const React = require('react');
11
11
  const axios = require('axios');
12
+ const ahooks = require('ahooks');
13
+ const constants = require('../../libs/constants.js');
12
14
  const useMobile = require('../../hooks/use-mobile.js');
13
15
  const useSpaceInfo = require('../../hooks/use-space-info.js');
14
16
  const useLocale = require('../../hooks/use-locale.js');
@@ -23,61 +25,83 @@ const spaceConnectError = require('../../icons/space-connect-error.svg.js');
23
25
  function Status({
24
26
  spaceUrl,
25
27
  status,
26
- isError,
28
+ errorCode,
27
29
  refresh,
28
30
  sx,
29
31
  ...rest
30
32
  }) {
31
- const { t } = useLocale();
33
+ const { t, locale } = useLocale();
32
34
  const iconStyle = { marginRight: "4px" };
33
- let icon = null;
34
- let text = null;
35
- if (status === index.SpaceStatus.CONNECTED) {
36
- icon = /* @__PURE__ */ jsxRuntime.jsx(spaceConnected, { style: iconStyle });
37
- text = /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#047857" }, children: t("storage.spaces.connected.tag") });
38
- }
39
- if (status === index.SpaceStatus.DISCONNECTED) {
40
- icon = /* @__PURE__ */ jsxRuntime.jsx(spaceDisconnect, { style: iconStyle });
41
- text = /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#626a77" }, children: t("storage.spaces.disconnected.tag") });
42
- }
43
- if (status === index.SpaceStatus.EXPIRED) {
44
- icon = /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle });
45
- text = /* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title: t("storage.spaces.error.expired"), placement: "top", children: /* @__PURE__ */ jsxRuntime.jsxs(
46
- material.Link,
47
- {
48
- href: ufo.withQuery(ufo.joinURL(spaceUrl, "overview"), { guide: 1 }),
49
- target: "_blank",
50
- underline: "hover",
51
- color: "error",
52
- sx: { display: "flex", alignItems: "center", color: "error.main" },
53
- children: [
54
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("storage.spaces.error.tag") }),
55
- /* @__PURE__ */ jsxRuntime.jsx(OpenInNewIcon, { sx: { fontSize: "14px", marginLeft: "4px" } })
56
- ]
35
+ const spaceGuideUrl = ufo.withQuery(ufo.joinURL(spaceUrl, "overview"), { guide: 1 });
36
+ const statusConfig = ahooks.useCreation(
37
+ () => ({
38
+ // 加载中
39
+ [index.SpaceStatus.LOADING]: {
40
+ icon: null,
41
+ text: null
42
+ },
43
+ // 已连接
44
+ [index.SpaceStatus.CONNECTED]: {
45
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceConnected, { style: iconStyle }),
46
+ text: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#047857" }, children: t("storage.spaces.connected.tag") })
47
+ },
48
+ // 未连接(未授权)
49
+ [index.SpaceStatus.DISCONNECTED]: {
50
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceDisconnect, { style: iconStyle }),
51
+ text: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#626a77" }, children: t("storage.spaces.disconnected.tag") })
57
52
  }
58
- ) });
59
- }
60
- if (status === index.SpaceStatus.CORS_BLOCKED) {
61
- icon = /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle });
62
- text = /* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title: t("storage.spaces.error.corsBlocked"), placement: "top", children: /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { display: "flex", alignItems: "center", color: "error.main" }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("storage.spaces.error.tag") }) }) });
63
- }
64
- if (status === index.SpaceStatus.UNKNOWN) {
65
- icon = /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle });
66
- text = /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { color: "error.main" }, children: t("storage.spaces.error.networkError") });
67
- }
53
+ }),
54
+ [locale]
55
+ );
56
+ const errorStatusConfig = ahooks.useCreation(
57
+ () => ({
58
+ // 未知的网络错误(如超时)
59
+ [constants.SPACE_CONNECT_ERROR_CODE.NETWORK_ERROR]: {
60
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle }),
61
+ text: /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { color: "error.main" }, children: t("storage.spaces.error.networkError") })
62
+ },
63
+ // 订阅过期
64
+ [constants.SPACE_CONNECT_ERROR_CODE.EXPIRED]: {
65
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle }),
66
+ text: /* @__PURE__ */ jsxRuntime.jsx(ErrorLink, { title: t("storage.spaces.error.expired"), url: spaceGuideUrl })
67
+ },
68
+ // 订阅逾期
69
+ [constants.SPACE_CONNECT_ERROR_CODE.PAST_DUE]: {
70
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle }),
71
+ text: /* @__PURE__ */ jsxRuntime.jsx(ErrorLink, { title: t("storage.spaces.error.pastDue"), url: spaceGuideUrl })
72
+ },
73
+ // 订阅状态异常
74
+ [constants.SPACE_CONNECT_ERROR_CODE.INVALID]: {
75
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle }),
76
+ text: /* @__PURE__ */ jsxRuntime.jsx(ErrorLink, { title: t("storage.spaces.error.invalid"), url: spaceGuideUrl })
77
+ },
78
+ // 用量不足
79
+ [constants.SPACE_CONNECT_ERROR_CODE.UNIT_LIMIT]: {
80
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle }),
81
+ text: /* @__PURE__ */ jsxRuntime.jsx(ErrorLink, { title: t("storage.spaces.error.unitLimit"), url: spaceGuideUrl })
82
+ },
83
+ // CORS 拦截
84
+ [constants.SPACE_CONNECT_ERROR_CODE.CORS_BLOCKED]: {
85
+ icon: /* @__PURE__ */ jsxRuntime.jsx(spaceConnectError, { style: iconStyle }),
86
+ text: /* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title: t("storage.spaces.error.corsBlocked"), placement: "top", children: /* @__PURE__ */ jsxRuntime.jsx(material.Box, { sx: { display: "flex", alignItems: "center", color: "error.main" }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("storage.spaces.error.tag") }) }) })
87
+ }
88
+ }),
89
+ [locale]
90
+ );
91
+ const { icon, text } = status === index.SpaceStatus.UNAVAILABLE ? errorStatusConfig[errorCode] : statusConfig[status];
68
92
  React.useEffect(() => {
69
93
  const handleVisibilityChange = () => {
70
94
  if (!document.hidden) {
71
95
  refresh();
72
96
  }
73
97
  };
74
- if (isError) {
98
+ if (status !== index.SpaceStatus.CONNECTED) {
75
99
  document.addEventListener("visibilitychange", handleVisibilityChange);
76
100
  } else {
77
101
  document.removeEventListener("visibilitychange", handleVisibilityChange);
78
102
  }
79
103
  return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
80
- }, [isError, refresh]);
104
+ }, [status, refresh]);
81
105
  return /* @__PURE__ */ jsxRuntime.jsxs(
82
106
  material.Box,
83
107
  {
@@ -92,6 +116,23 @@ function Status({
92
116
  }
93
117
  );
94
118
  }
119
+ function ErrorLink({ title, url }) {
120
+ const { t } = useLocale();
121
+ return /* @__PURE__ */ jsxRuntime.jsx(material.Tooltip, { title, placement: "top", children: /* @__PURE__ */ jsxRuntime.jsxs(
122
+ material.Link,
123
+ {
124
+ href: url,
125
+ target: "_blank",
126
+ underline: "hover",
127
+ color: "error",
128
+ sx: { display: "flex", alignItems: "center", color: "error.main" },
129
+ children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("storage.spaces.error.tag") }),
131
+ /* @__PURE__ */ jsxRuntime.jsx(OpenInNewIcon, { sx: { fontSize: "14px", marginLeft: "4px" } })
132
+ ]
133
+ }
134
+ ) });
135
+ }
95
136
  function SpaceCard({ endpoint, selected = false, compat, action, className, deps, ...rest }) {
96
137
  const isMobile = useMobile();
97
138
  const { t } = useLocale();
@@ -113,33 +154,30 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
113
154
  deps: [refreshSpaceInfo].concat(deps ?? [])
114
155
  });
115
156
  let spaceName = "";
116
- const hasPermission = spaceInfo?.hasPermission ?? false;
117
157
  const isAvailable = spaceInfo?.isAvailable ?? false;
118
- let isError = false;
119
158
  const spaceStatus = React.useRef(index.SpaceStatus.LOADING);
159
+ let errorCode = 0;
120
160
  if (loading) {
121
161
  spaceStatus.current = index.SpaceStatus.LOADING;
122
162
  spaceName = /* @__PURE__ */ jsxRuntime.jsx(material.Skeleton, { variant: "text", sx: { width: "180px", fontSize: "1rem" } });
123
163
  } else if (spaceInfo) {
124
164
  spaceName = spaceInfo.spaceName;
125
165
  if (!isAvailable) {
126
- spaceStatus.current = index.SpaceStatus.EXPIRED;
127
- isError = true;
128
- } else if (hasPermission) {
129
- spaceStatus.current = index.SpaceStatus.CONNECTED;
166
+ spaceStatus.current = index.SpaceStatus.UNAVAILABLE;
167
+ errorCode = spaceInfo.errorCode;
168
+ if (errorCode === constants.SPACE_CONNECT_ERROR_CODE.UNAUTHORIZED) {
169
+ spaceStatus.current = index.SpaceStatus.DISCONNECTED;
170
+ }
130
171
  } else {
131
- spaceStatus.current = index.SpaceStatus.DISCONNECTED;
172
+ spaceStatus.current = index.SpaceStatus.CONNECTED;
132
173
  }
133
174
  } else if (error instanceof axios.AxiosError) {
134
175
  spaceName = t("common.unknown");
135
- isError = true;
136
- if (!error.response && // 无响应
137
- error.message === "Network Error" && // 网络错误
138
- error.code !== "ECONNABORTED" && // 非超时引起
139
- new URL(error.config.url).origin !== window.location.origin) {
140
- spaceStatus.current = index.SpaceStatus.CORS_BLOCKED;
176
+ spaceStatus.current = index.SpaceStatus.UNAVAILABLE;
177
+ if (util.isCorsBlockedError(error)) {
178
+ errorCode = constants.SPACE_CONNECT_ERROR_CODE.CORS_BLOCKED;
141
179
  } else {
142
- spaceStatus.current = index.SpaceStatus.UNKNOWN;
180
+ errorCode = constants.SPACE_CONNECT_ERROR_CODE.NETWORK_ERROR;
143
181
  }
144
182
  }
145
183
  const renderAction = () => {
@@ -154,6 +192,7 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
154
192
  endpoint
155
193
  },
156
194
  spaceStatus: spaceStatus.current,
195
+ errorCode,
157
196
  selected,
158
197
  compat: isCompact,
159
198
  refresh
@@ -166,7 +205,7 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
166
205
  {
167
206
  className: util.classNames(className, {
168
207
  selected,
169
- error: isError
208
+ error: errorCode > 0
170
209
  }),
171
210
  ...rest,
172
211
  children: [
@@ -180,8 +219,8 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
180
219
  {
181
220
  spaceUrl,
182
221
  status: spaceStatus.current,
222
+ errorCode,
183
223
  refresh,
184
- isError,
185
224
  sx: { mr: 1 }
186
225
  }
187
226
  )
@@ -210,8 +249,8 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
210
249
  {
211
250
  spaceUrl,
212
251
  status: spaceStatus.current,
252
+ errorCode,
213
253
  refresh,
214
- isError,
215
254
  flexShrink: 0
216
255
  }
217
256
  ),
@@ -3,6 +3,8 @@ export default function useSpaceInfo({ endpoint, deps }: {
3
3
  deps?: any[];
4
4
  }): import("ahooks/lib/useRequest/src/types").Result<{
5
5
  spaceName: string;
6
- hasPermission: boolean;
6
+ subscriptionId: any;
7
7
  isAvailable: boolean;
8
+ errorMessage: string;
9
+ errorCode: number;
8
10
  } | undefined, []>;
@@ -11,13 +11,18 @@ function useSpaceInfo({ endpoint, deps = [] }) {
11
11
  return void 0;
12
12
  }
13
13
  const { headers } = await api.head(endpoint, {
14
- timeout: 1e3 * 30
14
+ timeout: 1e3 * 30,
15
+ params: {
16
+ withExtras: true
17
+ }
15
18
  });
16
19
  const spaceName = headers["x-space-name"];
17
20
  return {
18
21
  spaceName: spaceName ? decodeURIComponent(spaceName) : "",
19
- hasPermission: headers["x-listable"] === "true" && headers["x-readable"] === "true" && headers["x-writeable"] === "true",
20
- isAvailable: headers["x-space-is-available"] === "true"
22
+ subscriptionId: headers["x-subscription-id"],
23
+ isAvailable: headers["x-space-is-available"] === "true",
24
+ errorMessage: decodeURIComponent(headers["x-error-message"]),
25
+ errorCode: parseInt(headers["x-error-code"], 10)
21
26
  };
22
27
  },
23
28
  {
@@ -7,6 +7,7 @@ export * from './components/preview-space-nft';
7
7
  export * from './components/base-connect-to';
8
8
  export * from './components/auth-connect-to';
9
9
  export * from './components/session-connect-to';
10
+ export * from './libs/constants';
10
11
  export * from './libs/util';
11
12
  export * from './libs/gateway';
12
13
  export * from './locales';
package/dist/cjs/index.js CHANGED
@@ -9,6 +9,7 @@ const index$1 = require('./components/preview-space-nft/index.js');
9
9
  const index$2 = require('./components/base-connect-to/index.js');
10
10
  const index$3 = require('./components/auth-connect-to/index.js');
11
11
  const index$4 = require('./components/session-connect-to/index.js');
12
+ const constants = require('./libs/constants.js');
12
13
  const util = require('./libs/util.js');
13
14
  const gateway = require('./libs/gateway.js');
14
15
  const index$5 = require('./locales/index.js');
@@ -33,17 +34,22 @@ exports.PreviewSpaceNft = index$1.PreviewSpaceNft;
33
34
  exports.BaseConnectTo = index$2.BaseConnectTo;
34
35
  exports.AuthConnectTo = index$3.AuthConnectTo;
35
36
  exports.SessionConnectTo = index$4.SessionConnectTo;
37
+ exports.AUTHORIZE = constants.AUTHORIZE;
38
+ exports.SPACE_CONNECT_ERROR_CODE = constants.SPACE_CONNECT_ERROR_CODE;
39
+ exports.copyGatewayPageUrl = constants.copyGatewayPageUrl;
36
40
  exports.classNames = util.classNames;
37
41
  exports.decryptSpaceGateway = util.decryptSpaceGateway;
38
42
  exports.extraDIDSpacesCoreUrl = util.extraDIDSpacesCoreUrl;
39
43
  exports.getDIDSpaceDidFromEndpoint = util.getDIDSpaceDidFromEndpoint;
40
44
  exports.getDIDSpaceUrlFromEndpoint = util.getDIDSpaceUrlFromEndpoint;
41
45
  exports.getSpaceDidFromEndpoint = util.getSpaceDidFromEndpoint;
42
- exports.getSpaceDidFromGatewayUrl = util.getSpaceDidFromGatewayUrl;
46
+ exports.getSpaceDidFromSpaceUrl = util.getSpaceDidFromSpaceUrl;
43
47
  exports.getSpaceGatewayUrlFromEndpoint = util.getSpaceGatewayUrlFromEndpoint;
44
48
  exports.getSpaceNftDisplayUrlFromEndpoint = util.getSpaceNftDisplayUrlFromEndpoint;
49
+ exports.isCorsBlockedError = util.isCorsBlockedError;
50
+ exports.t = util.t;
45
51
  exports.getSpaceGatewayUrl = gateway.getSpaceGatewayUrl;
46
- exports.isValidSpaceGatewayUrl = gateway.isValidSpaceGatewayUrl;
52
+ exports.verifySpaceUrl = gateway.verifySpaceUrl;
47
53
  exports.translations = index$5.translations;
48
54
  exports.SpaceStatus = index$6.SpaceStatus;
49
55
  exports.EmptySpacesNFTIcon = emptySpaceNft;
@@ -1,3 +1,19 @@
1
+ export declare const SPACE_CONNECT_ERROR_CODE: {
2
+ /** 网络错误 */
3
+ NETWORK_ERROR: number;
4
+ /** 未授权 */
5
+ UNAUTHORIZED: number;
6
+ /** 订阅过期 */
7
+ EXPIRED: number;
8
+ /** 订阅逾期 */
9
+ PAST_DUE: number;
10
+ /** 订阅状态异常 */
11
+ INVALID: number;
12
+ /** 用量不足 */
13
+ UNIT_LIMIT: number;
14
+ /** 跨域限制 */
15
+ CORS_BLOCKED: number;
16
+ };
1
17
  export declare const AUTHORIZE: {
2
18
  DEFAULT_SCOPE: string;
3
19
  };
@@ -1,9 +1,26 @@
1
1
  'use strict';
2
2
 
3
+ const SPACE_CONNECT_ERROR_CODE = {
4
+ /** 网络错误 */
5
+ NETWORK_ERROR: 1001,
6
+ /** 未授权 */
7
+ UNAUTHORIZED: 1002,
8
+ /** 订阅过期 */
9
+ EXPIRED: 1003,
10
+ /** 订阅逾期 */
11
+ PAST_DUE: 1004,
12
+ /** 订阅状态异常 */
13
+ INVALID: 1005,
14
+ /** 用量不足 */
15
+ UNIT_LIMIT: 1006,
16
+ /** 跨域限制 */
17
+ CORS_BLOCKED: 1007
18
+ };
3
19
  const AUTHORIZE = {
4
20
  DEFAULT_SCOPE: "list:object read:object write:object"
5
21
  };
6
22
  const copyGatewayPageUrl = "https://www.arcblock.io/content/docs/did-spaces/how-to-obtain-space-gateway-url";
7
23
 
8
24
  exports.AUTHORIZE = AUTHORIZE;
25
+ exports.SPACE_CONNECT_ERROR_CODE = SPACE_CONNECT_ERROR_CODE;
9
26
  exports.copyGatewayPageUrl = copyGatewayPageUrl;
@@ -2,4 +2,8 @@ export declare function getSpaceGatewayUrl(anyUrl: string, options?: {
2
2
  timeout: number;
3
3
  withSearchParams: boolean;
4
4
  }): Promise<string>;
5
- export declare function isValidSpaceGatewayUrl(spaceGatewayUrl: string): Promise<boolean>;
5
+ export declare function verifySpaceUrl({ spaceGatewayUrl, spaceDid, locale, }: {
6
+ spaceGatewayUrl: string;
7
+ spaceDid?: string;
8
+ locale?: string;
9
+ }): Promise<void>;
@@ -4,6 +4,7 @@ const ufo = require('ufo');
4
4
  const isUrl = require('is-url');
5
5
  const isString = require('lodash/isString');
6
6
  const isObject = require('lodash/isObject');
7
+ const did = require('@arcblock/did');
7
8
  const api = require('./api.js');
8
9
  const util = require('./util.js');
9
10
 
@@ -26,22 +27,26 @@ async function getSpaceGatewayUrl(anyUrl, options = { timeout: 3e4, withSearchPa
26
27
  const spaceGatewayUrl = options.withSearchParams && u.searchParams.get("spaceDid") ? ufo.withQuery(didSpacesCoreUrl, { spaceDid: u.searchParams.get("spaceDid") }) : didSpacesCoreUrl;
27
28
  return spaceGatewayUrl;
28
29
  }
29
- async function isValidSpaceGatewayUrl(spaceGatewayUrl) {
30
- try {
31
- if (!isUrl(spaceGatewayUrl)) {
32
- return false;
33
- }
34
- const didSpacesCoreUrl = util.extraDIDSpacesCoreUrl(spaceGatewayUrl);
35
- const didConnectTokenUrl = ufo.joinURL(didSpacesCoreUrl, "space/api/did/one-click-authorization/token");
36
- const { status, data } = await api.get(didConnectTokenUrl, {
37
- timeout: 5e3
38
- });
39
- return status === 200 && isObject(data);
40
- } catch (error) {
41
- console.error(error);
42
- return false;
30
+ async function verifySpaceUrl({
31
+ spaceGatewayUrl,
32
+ spaceDid,
33
+ locale
34
+ }) {
35
+ if (!isUrl(spaceGatewayUrl)) {
36
+ throw new Error(util.t("common.invalidUrl", locale, { url: spaceGatewayUrl }));
37
+ }
38
+ if (!spaceDid || !did.isValid(spaceDid)) {
39
+ throw new Error(util.t("storage.spaces.gateway.add.invalidUrl", locale));
40
+ }
41
+ const didSpacesCoreUrl = util.extraDIDSpacesCoreUrl(spaceGatewayUrl);
42
+ const didConnectTokenUrl = ufo.joinURL(didSpacesCoreUrl, "space/api/did/one-click-authorization/token");
43
+ const { status, data } = await api.get(didConnectTokenUrl, {
44
+ timeout: 5e3
45
+ });
46
+ if (!(status === 200 && isObject(data))) {
47
+ throw new Error(util.t("storage.spaces.gateway.add.invalidUrl", locale));
43
48
  }
44
49
  }
45
50
 
46
51
  exports.getSpaceGatewayUrl = getSpaceGatewayUrl;
47
- exports.isValidSpaceGatewayUrl = isValidSpaceGatewayUrl;
52
+ exports.verifySpaceUrl = verifySpaceUrl;
@@ -1,3 +1,4 @@
1
+ import { type AxiosError } from 'axios';
1
2
  type ClassStr = string | undefined | null;
2
3
  /** classObj -> className
3
4
  * examples:
@@ -16,6 +17,9 @@ export declare function getSpaceNftDisplayUrlFromEndpoint(endpoint: string): str
16
17
  export declare function getSpaceDidFromEndpoint(endpoint: string): string | undefined;
17
18
  export declare function getSpaceGatewayUrlFromEndpoint(endpoint: string): string;
18
19
  export declare function extraDIDSpacesCoreUrl(url: string): string;
19
- export declare function getSpaceDidFromGatewayUrl(url: string): string | undefined;
20
+ export declare function getSpaceDidFromSpaceUrl(url: string): string | undefined;
20
21
  export declare function decryptSpaceGateway(response: Record<string, string>, decrypt: Function): any;
22
+ export declare function isCorsBlockedError(error: AxiosError): boolean;
23
+ export declare function t(key: string, locale?: string, data?: Record<string, any>): string;
24
+ export declare function t(key: string, data?: Record<string, any>): string;
21
25
  export {};
@@ -2,6 +2,8 @@
2
2
 
3
3
  const ufo = require('ufo');
4
4
  const isEmpty = require('lodash/isEmpty');
5
+ const util = require('@arcblock/ux/lib/Locale/util');
6
+ const index = require('../locales/index.js');
5
7
 
6
8
  function classNames(...classes) {
7
9
  const result = [];
@@ -54,7 +56,7 @@ function extraDIDSpacesCoreUrl(url) {
54
56
  return didSpacesCoreUrl;
55
57
  }
56
58
  const spaceDidRegex = /\/space\/([^/]+)/;
57
- function getSpaceDidFromGatewayUrl(url) {
59
+ function getSpaceDidFromSpaceUrl(url) {
58
60
  return new URL(url).pathname.match(spaceDidRegex)?.[1];
59
61
  }
60
62
  function decryptSpaceGateway(response, decrypt) {
@@ -70,6 +72,17 @@ function decryptSpaceGateway(response, decrypt) {
70
72
  ...space
71
73
  };
72
74
  }
75
+ function isCorsBlockedError(error) {
76
+ return !error.response && // 无响应
77
+ error.message === "Network Error" && // 是网络错误
78
+ error.code !== "ECONNABORTED" && // 非超时引起
79
+ new URL(error.config.url).origin !== window.location.origin;
80
+ }
81
+ function t(key, localeOrData, data = {}) {
82
+ const locale = typeof localeOrData === "string" ? localeOrData : "en";
83
+ const finalData = typeof localeOrData === "object" ? localeOrData : data;
84
+ return util.translate(index.translations, key, locale, "en", finalData);
85
+ }
73
86
 
74
87
  exports.classNames = classNames;
75
88
  exports.decryptSpaceGateway = decryptSpaceGateway;
@@ -77,6 +90,8 @@ exports.extraDIDSpacesCoreUrl = extraDIDSpacesCoreUrl;
77
90
  exports.getDIDSpaceDidFromEndpoint = getDIDSpaceDidFromEndpoint;
78
91
  exports.getDIDSpaceUrlFromEndpoint = getDIDSpaceUrlFromEndpoint;
79
92
  exports.getSpaceDidFromEndpoint = getSpaceDidFromEndpoint;
80
- exports.getSpaceDidFromGatewayUrl = getSpaceDidFromGatewayUrl;
93
+ exports.getSpaceDidFromSpaceUrl = getSpaceDidFromSpaceUrl;
81
94
  exports.getSpaceGatewayUrlFromEndpoint = getSpaceGatewayUrlFromEndpoint;
82
95
  exports.getSpaceNftDisplayUrlFromEndpoint = getSpaceNftDisplayUrlFromEndpoint;
96
+ exports.isCorsBlockedError = isCorsBlockedError;
97
+ exports.t = t;
@@ -9,7 +9,8 @@ const en = flat.flatten({
9
9
  delete: "Delete",
10
10
  error: "Error",
11
11
  open: "Open",
12
- unknown: "Unknown"
12
+ unknown: "Unknown",
13
+ invalidUrl: "Invalid url: {url}"
13
14
  },
14
15
  storage: {
15
16
  spaces: {
@@ -34,8 +35,11 @@ const en = flat.flatten({
34
35
  },
35
36
  error: {
36
37
  tag: "Need Attention",
37
- expired: "This DID spaces has expired, you can re-subscribe by opening this link",
38
- corsBlocked: "Connection requests may be blocked by CORS. Please check security settings in the DID Space dashboard.",
38
+ expired: "DID Spaces has expired, click this link to re-subscribe",
39
+ pastDue: "DID Spaces has unpaid bills, click this link to complete payment",
40
+ invalid: "DID Spaces subscription anomaly, click this link to check",
41
+ unitLimit: "DID Spaces free quota has been used up, click this link to pay for the upgrade",
42
+ corsBlocked: "Connection requests may be blocked by CORS. Please contact your DID Space administrator to check the settings",
39
43
  networkError: "Network error"
40
44
  },
41
45
  gateway: {
@@ -9,7 +9,8 @@ const zh = flat.flatten({
9
9
  delete: "\u5220\u9664",
10
10
  error: "\u9519\u8BEF",
11
11
  open: "\u6253\u5F00",
12
- unknown: "\u672A\u77E5"
12
+ unknown: "\u672A\u77E5",
13
+ invalidUrl: "\u65E0\u6548\u7684 url: {url}"
13
14
  },
14
15
  storage: {
15
16
  spaces: {
@@ -34,8 +35,11 @@ const zh = flat.flatten({
34
35
  },
35
36
  error: {
36
37
  tag: "\u9700\u8981\u5173\u6CE8",
37
- expired: "\u6B64 DID Spaces \u5DF2\u8FC7\u671F\uFF0C\u60A8\u53EF\u4EE5\u6253\u5F00\u6B64\u94FE\u63A5\u91CD\u65B0\u8BA2\u9605",
38
- corsBlocked: "\u8FDE\u63A5\u8BF7\u6C42\u53EF\u80FD\u88AB CORS \u963B\u6B62\u3002\u8BF7\u68C0\u67E5 DID Space \u63A7\u5236\u9762\u677F\u4E2D\u7684\u5B89\u5168\u8BBE\u7F6E\u3002",
38
+ expired: "DID Spaces \u5DF2\u8FC7\u671F\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u91CD\u65B0\u8BA2\u9605",
39
+ pastDue: "DID Spaces \u6709\u672A\u652F\u4ED8\u8D26\u5355\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u5B8C\u6210\u652F\u4ED8",
40
+ invalid: "DID Spaces \u8BA2\u9605\u5F02\u5E38\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u68C0\u67E5",
41
+ unitLimit: "DID Spaces \u514D\u8D39\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u4ED8\u8D39\u5347\u7EA7",
42
+ corsBlocked: "\u8FDE\u63A5\u8BF7\u6C42\u53EF\u80FD\u88AB CORS \u9650\u5236\uFF0C\u8BF7\u8054\u7CFB DID Space \u7BA1\u7406\u5458\u68C0\u67E5\u76F8\u5173\u8BBE\u7F6E",
39
43
  networkError: "\u7F51\u7EDC\u9519\u8BEF"
40
44
  },
41
45
  gateway: {
@@ -20,9 +20,8 @@ export declare enum SpaceStatus {
20
20
  LOADING = "loading",
21
21
  CONNECTED = "connected",
22
22
  DISCONNECTED = "disconnected",
23
- EXPIRED = "expired",
24
- CORS_BLOCKED = "corsBlocked",
25
- UNKNOWN = "unknown"
23
+ /** 不可用,跟订阅状态相关,比如过期、逾期、用量不足等 */
24
+ UNAVAILABLE = "unavailable"
26
25
  }
27
26
  export interface SpaceGateway {
28
27
  did: string;
@@ -4,9 +4,7 @@ var SpaceStatus = /* @__PURE__ */ ((SpaceStatus2) => {
4
4
  SpaceStatus2["LOADING"] = "loading";
5
5
  SpaceStatus2["CONNECTED"] = "connected";
6
6
  SpaceStatus2["DISCONNECTED"] = "disconnected";
7
- SpaceStatus2["EXPIRED"] = "expired";
8
- SpaceStatus2["CORS_BLOCKED"] = "corsBlocked";
9
- SpaceStatus2["UNKNOWN"] = "unknown";
7
+ SpaceStatus2["UNAVAILABLE"] = "unavailable";
10
8
  return SpaceStatus2;
11
9
  })(SpaceStatus || {});
12
10
 
@@ -1,15 +1,15 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import { useState } from 'react';
3
- import { isValid } from '@arcblock/did';
4
3
  import { Typography, CircularProgress, DialogContentText, TextField, Tooltip, Link } from '@mui/material';
5
4
  import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
6
5
  import Button from '@arcblock/ux/lib/Button';
7
6
  import SplitButton from '@arcblock/ux/lib/SplitButton';
8
7
  import Dialog from '@arcblock/ux/lib/Dialog';
9
8
  import { withQuery } from 'ufo';
9
+ import { AxiosError } from 'axios';
10
10
  import { copyGatewayPageUrl } from '../../libs/constants.js';
11
- import { extraDIDSpacesCoreUrl, getSpaceDidFromGatewayUrl } from '../../libs/util.js';
12
- import { getSpaceGatewayUrl, isValidSpaceGatewayUrl } from '../../libs/gateway.js';
11
+ import { extraDIDSpacesCoreUrl, getSpaceDidFromSpaceUrl, isCorsBlockedError } from '../../libs/util.js';
12
+ import { getSpaceGatewayUrl, verifySpaceUrl } from '../../libs/gateway.js';
13
13
  import useLocale from '../../hooks/use-locale.js';
14
14
 
15
15
  function BaseConnectTo({ style, onWalletClick, onGatewayConfirm, ...rest }) {
@@ -23,16 +23,17 @@ function BaseConnectTo({ style, onWalletClick, onGatewayConfirm, ...rest }) {
23
23
  setLoading(true);
24
24
  const spaceGatewayUrl = await getSpaceGatewayUrl(url);
25
25
  const didSpacesCoreUrl = extraDIDSpacesCoreUrl(spaceGatewayUrl);
26
- const spaceDid = getSpaceDidFromGatewayUrl(url);
27
- if (!isValid(spaceDid) || !await isValidSpaceGatewayUrl(didSpacesCoreUrl)) {
28
- throw new Error(t("storage.spaces.gateway.add.invalidUrl"));
29
- }
26
+ const spaceDid = getSpaceDidFromSpaceUrl(url);
27
+ await verifySpaceUrl({ spaceGatewayUrl, spaceDid, locale });
30
28
  onGatewayConfirm?.({
31
29
  spaceDid,
32
30
  spaceGatewayUrl: didSpacesCoreUrl
33
31
  });
34
32
  setOpen(false);
35
33
  } catch (err) {
34
+ if (err instanceof AxiosError && isCorsBlockedError(err)) {
35
+ err.message = t("storage.spaces.error.corsBlocked");
36
+ }
36
37
  console.error(err);
37
38
  setErrorMessage(err.message);
38
39
  } finally {
@@ -3,6 +3,7 @@ import { SpaceGateway, SpaceStatus } from '../../types';
3
3
  export type Action = React.ReactNode | ((props: {
4
4
  spaceGateway: SpaceGateway;
5
5
  spaceStatus: SpaceStatus;
6
+ errorCode: number;
6
7
  selected: boolean;
7
8
  compat: boolean;
8
9
  refresh: () => void;
@@ -7,10 +7,12 @@ import DidAddress from '@arcblock/ux/lib/DID';
7
7
  import { styled } from '@arcblock/ux/lib/Theme';
8
8
  import { useState, useRef, useEffect } from 'react';
9
9
  import { AxiosError } from 'axios';
10
+ import { useCreation } from 'ahooks';
11
+ import { SPACE_CONNECT_ERROR_CODE } from '../../libs/constants.js';
10
12
  import useMobile from '../../hooks/use-mobile.js';
11
13
  import useSpaceInfo from '../../hooks/use-space-info.js';
12
14
  import useLocale from '../../hooks/use-locale.js';
13
- import { getDIDSpaceDidFromEndpoint, getDIDSpaceUrlFromEndpoint, getSpaceGatewayUrlFromEndpoint, classNames, getSpaceNftDisplayUrlFromEndpoint } from '../../libs/util.js';
15
+ import { getDIDSpaceDidFromEndpoint, getDIDSpaceUrlFromEndpoint, getSpaceGatewayUrlFromEndpoint, isCorsBlockedError, classNames, getSpaceNftDisplayUrlFromEndpoint } from '../../libs/util.js';
14
16
  import { SpaceStatus } from '../../types/index.js';
15
17
  import '../../icons/index.js';
16
18
  import { PreviewSpaceNft } from '../preview-space-nft/index.js';
@@ -21,61 +23,83 @@ import SvgSpaceConnectError from '../../icons/space-connect-error.svg.js';
21
23
  function Status({
22
24
  spaceUrl,
23
25
  status,
24
- isError,
26
+ errorCode,
25
27
  refresh,
26
28
  sx,
27
29
  ...rest
28
30
  }) {
29
- const { t } = useLocale();
31
+ const { t, locale } = useLocale();
30
32
  const iconStyle = { marginRight: "4px" };
31
- let icon = null;
32
- let text = null;
33
- if (status === SpaceStatus.CONNECTED) {
34
- icon = /* @__PURE__ */ jsx(SvgSpaceConnected, { style: iconStyle });
35
- text = /* @__PURE__ */ jsx("span", { style: { color: "#047857" }, children: t("storage.spaces.connected.tag") });
36
- }
37
- if (status === SpaceStatus.DISCONNECTED) {
38
- icon = /* @__PURE__ */ jsx(SvgSpaceDisconnect, { style: iconStyle });
39
- text = /* @__PURE__ */ jsx("span", { style: { color: "#626a77" }, children: t("storage.spaces.disconnected.tag") });
40
- }
41
- if (status === SpaceStatus.EXPIRED) {
42
- icon = /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle });
43
- text = /* @__PURE__ */ jsx(Tooltip, { title: t("storage.spaces.error.expired"), placement: "top", children: /* @__PURE__ */ jsxs(
44
- Link,
45
- {
46
- href: withQuery(joinURL(spaceUrl, "overview"), { guide: 1 }),
47
- target: "_blank",
48
- underline: "hover",
49
- color: "error",
50
- sx: { display: "flex", alignItems: "center", color: "error.main" },
51
- children: [
52
- /* @__PURE__ */ jsx("span", { children: t("storage.spaces.error.tag") }),
53
- /* @__PURE__ */ jsx(OpenInNewIcon, { sx: { fontSize: "14px", marginLeft: "4px" } })
54
- ]
33
+ const spaceGuideUrl = withQuery(joinURL(spaceUrl, "overview"), { guide: 1 });
34
+ const statusConfig = useCreation(
35
+ () => ({
36
+ // 加载中
37
+ [SpaceStatus.LOADING]: {
38
+ icon: null,
39
+ text: null
40
+ },
41
+ // 已连接
42
+ [SpaceStatus.CONNECTED]: {
43
+ icon: /* @__PURE__ */ jsx(SvgSpaceConnected, { style: iconStyle }),
44
+ text: /* @__PURE__ */ jsx("span", { style: { color: "#047857" }, children: t("storage.spaces.connected.tag") })
45
+ },
46
+ // 未连接(未授权)
47
+ [SpaceStatus.DISCONNECTED]: {
48
+ icon: /* @__PURE__ */ jsx(SvgSpaceDisconnect, { style: iconStyle }),
49
+ text: /* @__PURE__ */ jsx("span", { style: { color: "#626a77" }, children: t("storage.spaces.disconnected.tag") })
55
50
  }
56
- ) });
57
- }
58
- if (status === SpaceStatus.CORS_BLOCKED) {
59
- icon = /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle });
60
- text = /* @__PURE__ */ jsx(Tooltip, { title: t("storage.spaces.error.corsBlocked"), placement: "top", children: /* @__PURE__ */ jsx(Box, { sx: { display: "flex", alignItems: "center", color: "error.main" }, children: /* @__PURE__ */ jsx("span", { children: t("storage.spaces.error.tag") }) }) });
61
- }
62
- if (status === SpaceStatus.UNKNOWN) {
63
- icon = /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle });
64
- text = /* @__PURE__ */ jsx(Box, { sx: { color: "error.main" }, children: t("storage.spaces.error.networkError") });
65
- }
51
+ }),
52
+ [locale]
53
+ );
54
+ const errorStatusConfig = useCreation(
55
+ () => ({
56
+ // 未知的网络错误(如超时)
57
+ [SPACE_CONNECT_ERROR_CODE.NETWORK_ERROR]: {
58
+ icon: /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle }),
59
+ text: /* @__PURE__ */ jsx(Box, { sx: { color: "error.main" }, children: t("storage.spaces.error.networkError") })
60
+ },
61
+ // 订阅过期
62
+ [SPACE_CONNECT_ERROR_CODE.EXPIRED]: {
63
+ icon: /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle }),
64
+ text: /* @__PURE__ */ jsx(ErrorLink, { title: t("storage.spaces.error.expired"), url: spaceGuideUrl })
65
+ },
66
+ // 订阅逾期
67
+ [SPACE_CONNECT_ERROR_CODE.PAST_DUE]: {
68
+ icon: /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle }),
69
+ text: /* @__PURE__ */ jsx(ErrorLink, { title: t("storage.spaces.error.pastDue"), url: spaceGuideUrl })
70
+ },
71
+ // 订阅状态异常
72
+ [SPACE_CONNECT_ERROR_CODE.INVALID]: {
73
+ icon: /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle }),
74
+ text: /* @__PURE__ */ jsx(ErrorLink, { title: t("storage.spaces.error.invalid"), url: spaceGuideUrl })
75
+ },
76
+ // 用量不足
77
+ [SPACE_CONNECT_ERROR_CODE.UNIT_LIMIT]: {
78
+ icon: /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle }),
79
+ text: /* @__PURE__ */ jsx(ErrorLink, { title: t("storage.spaces.error.unitLimit"), url: spaceGuideUrl })
80
+ },
81
+ // CORS 拦截
82
+ [SPACE_CONNECT_ERROR_CODE.CORS_BLOCKED]: {
83
+ icon: /* @__PURE__ */ jsx(SvgSpaceConnectError, { style: iconStyle }),
84
+ text: /* @__PURE__ */ jsx(Tooltip, { title: t("storage.spaces.error.corsBlocked"), placement: "top", children: /* @__PURE__ */ jsx(Box, { sx: { display: "flex", alignItems: "center", color: "error.main" }, children: /* @__PURE__ */ jsx("span", { children: t("storage.spaces.error.tag") }) }) })
85
+ }
86
+ }),
87
+ [locale]
88
+ );
89
+ const { icon, text } = status === SpaceStatus.UNAVAILABLE ? errorStatusConfig[errorCode] : statusConfig[status];
66
90
  useEffect(() => {
67
91
  const handleVisibilityChange = () => {
68
92
  if (!document.hidden) {
69
93
  refresh();
70
94
  }
71
95
  };
72
- if (isError) {
96
+ if (status !== SpaceStatus.CONNECTED) {
73
97
  document.addEventListener("visibilitychange", handleVisibilityChange);
74
98
  } else {
75
99
  document.removeEventListener("visibilitychange", handleVisibilityChange);
76
100
  }
77
101
  return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
78
- }, [isError, refresh]);
102
+ }, [status, refresh]);
79
103
  return /* @__PURE__ */ jsxs(
80
104
  Box,
81
105
  {
@@ -90,6 +114,23 @@ function Status({
90
114
  }
91
115
  );
92
116
  }
117
+ function ErrorLink({ title, url }) {
118
+ const { t } = useLocale();
119
+ return /* @__PURE__ */ jsx(Tooltip, { title, placement: "top", children: /* @__PURE__ */ jsxs(
120
+ Link,
121
+ {
122
+ href: url,
123
+ target: "_blank",
124
+ underline: "hover",
125
+ color: "error",
126
+ sx: { display: "flex", alignItems: "center", color: "error.main" },
127
+ children: [
128
+ /* @__PURE__ */ jsx("span", { children: t("storage.spaces.error.tag") }),
129
+ /* @__PURE__ */ jsx(OpenInNewIcon, { sx: { fontSize: "14px", marginLeft: "4px" } })
130
+ ]
131
+ }
132
+ ) });
133
+ }
93
134
  function SpaceCard({ endpoint, selected = false, compat, action, className, deps, ...rest }) {
94
135
  const isMobile = useMobile();
95
136
  const { t } = useLocale();
@@ -111,33 +152,30 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
111
152
  deps: [refreshSpaceInfo].concat(deps ?? [])
112
153
  });
113
154
  let spaceName = "";
114
- const hasPermission = spaceInfo?.hasPermission ?? false;
115
155
  const isAvailable = spaceInfo?.isAvailable ?? false;
116
- let isError = false;
117
156
  const spaceStatus = useRef(SpaceStatus.LOADING);
157
+ let errorCode = 0;
118
158
  if (loading) {
119
159
  spaceStatus.current = SpaceStatus.LOADING;
120
160
  spaceName = /* @__PURE__ */ jsx(Skeleton, { variant: "text", sx: { width: "180px", fontSize: "1rem" } });
121
161
  } else if (spaceInfo) {
122
162
  spaceName = spaceInfo.spaceName;
123
163
  if (!isAvailable) {
124
- spaceStatus.current = SpaceStatus.EXPIRED;
125
- isError = true;
126
- } else if (hasPermission) {
127
- spaceStatus.current = SpaceStatus.CONNECTED;
164
+ spaceStatus.current = SpaceStatus.UNAVAILABLE;
165
+ errorCode = spaceInfo.errorCode;
166
+ if (errorCode === SPACE_CONNECT_ERROR_CODE.UNAUTHORIZED) {
167
+ spaceStatus.current = SpaceStatus.DISCONNECTED;
168
+ }
128
169
  } else {
129
- spaceStatus.current = SpaceStatus.DISCONNECTED;
170
+ spaceStatus.current = SpaceStatus.CONNECTED;
130
171
  }
131
172
  } else if (error instanceof AxiosError) {
132
173
  spaceName = t("common.unknown");
133
- isError = true;
134
- if (!error.response && // 无响应
135
- error.message === "Network Error" && // 网络错误
136
- error.code !== "ECONNABORTED" && // 非超时引起
137
- new URL(error.config.url).origin !== window.location.origin) {
138
- spaceStatus.current = SpaceStatus.CORS_BLOCKED;
174
+ spaceStatus.current = SpaceStatus.UNAVAILABLE;
175
+ if (isCorsBlockedError(error)) {
176
+ errorCode = SPACE_CONNECT_ERROR_CODE.CORS_BLOCKED;
139
177
  } else {
140
- spaceStatus.current = SpaceStatus.UNKNOWN;
178
+ errorCode = SPACE_CONNECT_ERROR_CODE.NETWORK_ERROR;
141
179
  }
142
180
  }
143
181
  const renderAction = () => {
@@ -152,6 +190,7 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
152
190
  endpoint
153
191
  },
154
192
  spaceStatus: spaceStatus.current,
193
+ errorCode,
155
194
  selected,
156
195
  compat: isCompact,
157
196
  refresh
@@ -164,7 +203,7 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
164
203
  {
165
204
  className: classNames(className, {
166
205
  selected,
167
- error: isError
206
+ error: errorCode > 0
168
207
  }),
169
208
  ...rest,
170
209
  children: [
@@ -178,8 +217,8 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
178
217
  {
179
218
  spaceUrl,
180
219
  status: spaceStatus.current,
220
+ errorCode,
181
221
  refresh,
182
- isError,
183
222
  sx: { mr: 1 }
184
223
  }
185
224
  )
@@ -208,8 +247,8 @@ function SpaceCard({ endpoint, selected = false, compat, action, className, deps
208
247
  {
209
248
  spaceUrl,
210
249
  status: spaceStatus.current,
250
+ errorCode,
211
251
  refresh,
212
- isError,
213
252
  flexShrink: 0
214
253
  }
215
254
  ),
@@ -3,6 +3,8 @@ export default function useSpaceInfo({ endpoint, deps }: {
3
3
  deps?: any[];
4
4
  }): import("ahooks/lib/useRequest/src/types").Result<{
5
5
  spaceName: string;
6
- hasPermission: boolean;
6
+ subscriptionId: any;
7
7
  isAvailable: boolean;
8
+ errorMessage: string;
9
+ errorCode: number;
8
10
  } | undefined, []>;
@@ -9,13 +9,18 @@ function useSpaceInfo({ endpoint, deps = [] }) {
9
9
  return void 0;
10
10
  }
11
11
  const { headers } = await api.head(endpoint, {
12
- timeout: 1e3 * 30
12
+ timeout: 1e3 * 30,
13
+ params: {
14
+ withExtras: true
15
+ }
13
16
  });
14
17
  const spaceName = headers["x-space-name"];
15
18
  return {
16
19
  spaceName: spaceName ? decodeURIComponent(spaceName) : "",
17
- hasPermission: headers["x-listable"] === "true" && headers["x-readable"] === "true" && headers["x-writeable"] === "true",
18
- isAvailable: headers["x-space-is-available"] === "true"
20
+ subscriptionId: headers["x-subscription-id"],
21
+ isAvailable: headers["x-space-is-available"] === "true",
22
+ errorMessage: decodeURIComponent(headers["x-error-message"]),
23
+ errorCode: parseInt(headers["x-error-code"], 10)
19
24
  };
20
25
  },
21
26
  {
@@ -7,6 +7,7 @@ export * from './components/preview-space-nft';
7
7
  export * from './components/base-connect-to';
8
8
  export * from './components/auth-connect-to';
9
9
  export * from './components/session-connect-to';
10
+ export * from './libs/constants';
10
11
  export * from './libs/util';
11
12
  export * from './libs/gateway';
12
13
  export * from './locales';
package/dist/es/index.js CHANGED
@@ -7,8 +7,9 @@ export { PreviewSpaceNft } from './components/preview-space-nft/index.js';
7
7
  export { BaseConnectTo } from './components/base-connect-to/index.js';
8
8
  export { AuthConnectTo } from './components/auth-connect-to/index.js';
9
9
  export { SessionConnectTo } from './components/session-connect-to/index.js';
10
- export { classNames, decryptSpaceGateway, extraDIDSpacesCoreUrl, getDIDSpaceDidFromEndpoint, getDIDSpaceUrlFromEndpoint, getSpaceDidFromEndpoint, getSpaceDidFromGatewayUrl, getSpaceGatewayUrlFromEndpoint, getSpaceNftDisplayUrlFromEndpoint } from './libs/util.js';
11
- export { getSpaceGatewayUrl, isValidSpaceGatewayUrl } from './libs/gateway.js';
10
+ export { AUTHORIZE, SPACE_CONNECT_ERROR_CODE, copyGatewayPageUrl } from './libs/constants.js';
11
+ export { classNames, decryptSpaceGateway, extraDIDSpacesCoreUrl, getDIDSpaceDidFromEndpoint, getDIDSpaceUrlFromEndpoint, getSpaceDidFromEndpoint, getSpaceDidFromSpaceUrl, getSpaceGatewayUrlFromEndpoint, getSpaceNftDisplayUrlFromEndpoint, isCorsBlockedError, t } from './libs/util.js';
12
+ export { getSpaceGatewayUrl, verifySpaceUrl } from './libs/gateway.js';
12
13
  export { translations } from './locales/index.js';
13
14
  export { SpaceStatus } from './types/index.js';
14
15
  import './icons/index.js';
@@ -1,3 +1,19 @@
1
+ export declare const SPACE_CONNECT_ERROR_CODE: {
2
+ /** 网络错误 */
3
+ NETWORK_ERROR: number;
4
+ /** 未授权 */
5
+ UNAUTHORIZED: number;
6
+ /** 订阅过期 */
7
+ EXPIRED: number;
8
+ /** 订阅逾期 */
9
+ PAST_DUE: number;
10
+ /** 订阅状态异常 */
11
+ INVALID: number;
12
+ /** 用量不足 */
13
+ UNIT_LIMIT: number;
14
+ /** 跨域限制 */
15
+ CORS_BLOCKED: number;
16
+ };
1
17
  export declare const AUTHORIZE: {
2
18
  DEFAULT_SCOPE: string;
3
19
  };
@@ -1,6 +1,22 @@
1
+ const SPACE_CONNECT_ERROR_CODE = {
2
+ /** 网络错误 */
3
+ NETWORK_ERROR: 1001,
4
+ /** 未授权 */
5
+ UNAUTHORIZED: 1002,
6
+ /** 订阅过期 */
7
+ EXPIRED: 1003,
8
+ /** 订阅逾期 */
9
+ PAST_DUE: 1004,
10
+ /** 订阅状态异常 */
11
+ INVALID: 1005,
12
+ /** 用量不足 */
13
+ UNIT_LIMIT: 1006,
14
+ /** 跨域限制 */
15
+ CORS_BLOCKED: 1007
16
+ };
1
17
  const AUTHORIZE = {
2
18
  DEFAULT_SCOPE: "list:object read:object write:object"
3
19
  };
4
20
  const copyGatewayPageUrl = "https://www.arcblock.io/content/docs/did-spaces/how-to-obtain-space-gateway-url";
5
21
 
6
- export { AUTHORIZE, copyGatewayPageUrl };
22
+ export { AUTHORIZE, SPACE_CONNECT_ERROR_CODE, copyGatewayPageUrl };
@@ -2,4 +2,8 @@ export declare function getSpaceGatewayUrl(anyUrl: string, options?: {
2
2
  timeout: number;
3
3
  withSearchParams: boolean;
4
4
  }): Promise<string>;
5
- export declare function isValidSpaceGatewayUrl(spaceGatewayUrl: string): Promise<boolean>;
5
+ export declare function verifySpaceUrl({ spaceGatewayUrl, spaceDid, locale, }: {
6
+ spaceGatewayUrl: string;
7
+ spaceDid?: string;
8
+ locale?: string;
9
+ }): Promise<void>;
@@ -2,8 +2,9 @@ import { joinURL, withQuery } from 'ufo';
2
2
  import isUrl from 'is-url';
3
3
  import isString from 'lodash/isString';
4
4
  import isObject from 'lodash/isObject';
5
+ import { isValid } from '@arcblock/did';
5
6
  import api from './api.js';
6
- import { extraDIDSpacesCoreUrl } from './util.js';
7
+ import { t, extraDIDSpacesCoreUrl } from './util.js';
7
8
 
8
9
  async function getSpaceGatewayUrl(anyUrl, options = { timeout: 3e4, withSearchParams: true }) {
9
10
  if (!isString(anyUrl)) {
@@ -24,21 +25,25 @@ async function getSpaceGatewayUrl(anyUrl, options = { timeout: 3e4, withSearchPa
24
25
  const spaceGatewayUrl = options.withSearchParams && u.searchParams.get("spaceDid") ? withQuery(didSpacesCoreUrl, { spaceDid: u.searchParams.get("spaceDid") }) : didSpacesCoreUrl;
25
26
  return spaceGatewayUrl;
26
27
  }
27
- async function isValidSpaceGatewayUrl(spaceGatewayUrl) {
28
- try {
29
- if (!isUrl(spaceGatewayUrl)) {
30
- return false;
31
- }
32
- const didSpacesCoreUrl = extraDIDSpacesCoreUrl(spaceGatewayUrl);
33
- const didConnectTokenUrl = joinURL(didSpacesCoreUrl, "space/api/did/one-click-authorization/token");
34
- const { status, data } = await api.get(didConnectTokenUrl, {
35
- timeout: 5e3
36
- });
37
- return status === 200 && isObject(data);
38
- } catch (error) {
39
- console.error(error);
40
- return false;
28
+ async function verifySpaceUrl({
29
+ spaceGatewayUrl,
30
+ spaceDid,
31
+ locale
32
+ }) {
33
+ if (!isUrl(spaceGatewayUrl)) {
34
+ throw new Error(t("common.invalidUrl", locale, { url: spaceGatewayUrl }));
35
+ }
36
+ if (!spaceDid || !isValid(spaceDid)) {
37
+ throw new Error(t("storage.spaces.gateway.add.invalidUrl", locale));
38
+ }
39
+ const didSpacesCoreUrl = extraDIDSpacesCoreUrl(spaceGatewayUrl);
40
+ const didConnectTokenUrl = joinURL(didSpacesCoreUrl, "space/api/did/one-click-authorization/token");
41
+ const { status, data } = await api.get(didConnectTokenUrl, {
42
+ timeout: 5e3
43
+ });
44
+ if (!(status === 200 && isObject(data))) {
45
+ throw new Error(t("storage.spaces.gateway.add.invalidUrl", locale));
41
46
  }
42
47
  }
43
48
 
44
- export { getSpaceGatewayUrl, isValidSpaceGatewayUrl };
49
+ export { getSpaceGatewayUrl, verifySpaceUrl };
@@ -1,3 +1,4 @@
1
+ import { type AxiosError } from 'axios';
1
2
  type ClassStr = string | undefined | null;
2
3
  /** classObj -> className
3
4
  * examples:
@@ -16,6 +17,9 @@ export declare function getSpaceNftDisplayUrlFromEndpoint(endpoint: string): str
16
17
  export declare function getSpaceDidFromEndpoint(endpoint: string): string | undefined;
17
18
  export declare function getSpaceGatewayUrlFromEndpoint(endpoint: string): string;
18
19
  export declare function extraDIDSpacesCoreUrl(url: string): string;
19
- export declare function getSpaceDidFromGatewayUrl(url: string): string | undefined;
20
+ export declare function getSpaceDidFromSpaceUrl(url: string): string | undefined;
20
21
  export declare function decryptSpaceGateway(response: Record<string, string>, decrypt: Function): any;
22
+ export declare function isCorsBlockedError(error: AxiosError): boolean;
23
+ export declare function t(key: string, locale?: string, data?: Record<string, any>): string;
24
+ export declare function t(key: string, data?: Record<string, any>): string;
21
25
  export {};
@@ -1,5 +1,7 @@
1
1
  import { joinURL } from 'ufo';
2
2
  import isEmpty from 'lodash/isEmpty';
3
+ import { translate } from '@arcblock/ux/lib/Locale/util';
4
+ import { translations } from '../locales/index.js';
3
5
 
4
6
  function classNames(...classes) {
5
7
  const result = [];
@@ -52,7 +54,7 @@ function extraDIDSpacesCoreUrl(url) {
52
54
  return didSpacesCoreUrl;
53
55
  }
54
56
  const spaceDidRegex = /\/space\/([^/]+)/;
55
- function getSpaceDidFromGatewayUrl(url) {
57
+ function getSpaceDidFromSpaceUrl(url) {
56
58
  return new URL(url).pathname.match(spaceDidRegex)?.[1];
57
59
  }
58
60
  function decryptSpaceGateway(response, decrypt) {
@@ -68,5 +70,16 @@ function decryptSpaceGateway(response, decrypt) {
68
70
  ...space
69
71
  };
70
72
  }
73
+ function isCorsBlockedError(error) {
74
+ return !error.response && // 无响应
75
+ error.message === "Network Error" && // 是网络错误
76
+ error.code !== "ECONNABORTED" && // 非超时引起
77
+ new URL(error.config.url).origin !== window.location.origin;
78
+ }
79
+ function t(key, localeOrData, data = {}) {
80
+ const locale = typeof localeOrData === "string" ? localeOrData : "en";
81
+ const finalData = typeof localeOrData === "object" ? localeOrData : data;
82
+ return translate(translations, key, locale, "en", finalData);
83
+ }
71
84
 
72
- export { classNames, decryptSpaceGateway, extraDIDSpacesCoreUrl, getDIDSpaceDidFromEndpoint, getDIDSpaceUrlFromEndpoint, getSpaceDidFromEndpoint, getSpaceDidFromGatewayUrl, getSpaceGatewayUrlFromEndpoint, getSpaceNftDisplayUrlFromEndpoint };
85
+ export { classNames, decryptSpaceGateway, extraDIDSpacesCoreUrl, getDIDSpaceDidFromEndpoint, getDIDSpaceUrlFromEndpoint, getSpaceDidFromEndpoint, getSpaceDidFromSpaceUrl, getSpaceGatewayUrlFromEndpoint, getSpaceNftDisplayUrlFromEndpoint, isCorsBlockedError, t };
@@ -7,7 +7,8 @@ const en = flatten({
7
7
  delete: "Delete",
8
8
  error: "Error",
9
9
  open: "Open",
10
- unknown: "Unknown"
10
+ unknown: "Unknown",
11
+ invalidUrl: "Invalid url: {url}"
11
12
  },
12
13
  storage: {
13
14
  spaces: {
@@ -32,8 +33,11 @@ const en = flatten({
32
33
  },
33
34
  error: {
34
35
  tag: "Need Attention",
35
- expired: "This DID spaces has expired, you can re-subscribe by opening this link",
36
- corsBlocked: "Connection requests may be blocked by CORS. Please check security settings in the DID Space dashboard.",
36
+ expired: "DID Spaces has expired, click this link to re-subscribe",
37
+ pastDue: "DID Spaces has unpaid bills, click this link to complete payment",
38
+ invalid: "DID Spaces subscription anomaly, click this link to check",
39
+ unitLimit: "DID Spaces free quota has been used up, click this link to pay for the upgrade",
40
+ corsBlocked: "Connection requests may be blocked by CORS. Please contact your DID Space administrator to check the settings",
37
41
  networkError: "Network error"
38
42
  },
39
43
  gateway: {
@@ -7,7 +7,8 @@ const zh = flatten({
7
7
  delete: "\u5220\u9664",
8
8
  error: "\u9519\u8BEF",
9
9
  open: "\u6253\u5F00",
10
- unknown: "\u672A\u77E5"
10
+ unknown: "\u672A\u77E5",
11
+ invalidUrl: "\u65E0\u6548\u7684 url: {url}"
11
12
  },
12
13
  storage: {
13
14
  spaces: {
@@ -32,8 +33,11 @@ const zh = flatten({
32
33
  },
33
34
  error: {
34
35
  tag: "\u9700\u8981\u5173\u6CE8",
35
- expired: "\u6B64 DID Spaces \u5DF2\u8FC7\u671F\uFF0C\u60A8\u53EF\u4EE5\u6253\u5F00\u6B64\u94FE\u63A5\u91CD\u65B0\u8BA2\u9605",
36
- corsBlocked: "\u8FDE\u63A5\u8BF7\u6C42\u53EF\u80FD\u88AB CORS \u963B\u6B62\u3002\u8BF7\u68C0\u67E5 DID Space \u63A7\u5236\u9762\u677F\u4E2D\u7684\u5B89\u5168\u8BBE\u7F6E\u3002",
36
+ expired: "DID Spaces \u5DF2\u8FC7\u671F\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u91CD\u65B0\u8BA2\u9605",
37
+ pastDue: "DID Spaces \u6709\u672A\u652F\u4ED8\u8D26\u5355\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u5B8C\u6210\u652F\u4ED8",
38
+ invalid: "DID Spaces \u8BA2\u9605\u5F02\u5E38\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u68C0\u67E5",
39
+ unitLimit: "DID Spaces \u514D\u8D39\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u70B9\u51FB\u6B64\u94FE\u63A5\u4ED8\u8D39\u5347\u7EA7",
40
+ corsBlocked: "\u8FDE\u63A5\u8BF7\u6C42\u53EF\u80FD\u88AB CORS \u9650\u5236\uFF0C\u8BF7\u8054\u7CFB DID Space \u7BA1\u7406\u5458\u68C0\u67E5\u76F8\u5173\u8BBE\u7F6E",
37
41
  networkError: "\u7F51\u7EDC\u9519\u8BEF"
38
42
  },
39
43
  gateway: {
@@ -20,9 +20,8 @@ export declare enum SpaceStatus {
20
20
  LOADING = "loading",
21
21
  CONNECTED = "connected",
22
22
  DISCONNECTED = "disconnected",
23
- EXPIRED = "expired",
24
- CORS_BLOCKED = "corsBlocked",
25
- UNKNOWN = "unknown"
23
+ /** 不可用,跟订阅状态相关,比如过期、逾期、用量不足等 */
24
+ UNAVAILABLE = "unavailable"
26
25
  }
27
26
  export interface SpaceGateway {
28
27
  did: string;
@@ -2,9 +2,7 @@ var SpaceStatus = /* @__PURE__ */ ((SpaceStatus2) => {
2
2
  SpaceStatus2["LOADING"] = "loading";
3
3
  SpaceStatus2["CONNECTED"] = "connected";
4
4
  SpaceStatus2["DISCONNECTED"] = "disconnected";
5
- SpaceStatus2["EXPIRED"] = "expired";
6
- SpaceStatus2["CORS_BLOCKED"] = "corsBlocked";
7
- SpaceStatus2["UNKNOWN"] = "unknown";
5
+ SpaceStatus2["UNAVAILABLE"] = "unavailable";
8
6
  return SpaceStatus2;
9
7
  })(SpaceStatus || {});
10
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/did-space-react",
3
- "version": "0.5.65",
3
+ "version": "0.5.67",
4
4
  "description": "Reusable react components for did space",
5
5
  "keywords": [
6
6
  "react",
@@ -85,8 +85,8 @@
85
85
  },
86
86
  "dependencies": {
87
87
  "@arcblock/did": "^1.18.139",
88
- "@arcblock/did-connect": "^2.10.63",
89
- "@arcblock/ux": "^2.10.63",
88
+ "@arcblock/did-connect": "^2.10.65",
89
+ "@arcblock/ux": "^2.10.65",
90
90
  "@blocklet/js-sdk": "1.16.32",
91
91
  "@blocklet/sdk": "1.16.32",
92
92
  "@mui/icons-material": "^5.16.7",
@@ -127,7 +127,7 @@
127
127
  "@types/react": "^18.3.12",
128
128
  "@types/react-dom": "^18.3.1",
129
129
  "@vitejs/plugin-legacy": "^5.4.3",
130
- "@vitest/coverage-v8": "^2.1.4",
130
+ "@vitest/coverage-v8": "^2.1.5",
131
131
  "babel-plugin-inline-react-svg": "^2.0.2",
132
132
  "copyfiles": "^2.4.1",
133
133
  "eslint": "^8.57.1",
@@ -140,10 +140,10 @@
140
140
  "type-fest": "^4.26.1",
141
141
  "typescript": "~5.5.4",
142
142
  "unbuild": "^2.0.0",
143
- "vite": "^5.4.10",
143
+ "vite": "^5.4.11",
144
144
  "vite-plugin-babel": "^1.2.0",
145
145
  "vite-plugin-node-polyfills": "^0.22.0",
146
- "vitest": "^2.1.4"
146
+ "vitest": "^2.1.5"
147
147
  },
148
- "gitHead": "08b31fa5ab4100de40cfa8b70eaf0504e6d35264"
148
+ "gitHead": "3e77ce650933ce3a1a1fdd7d869d40407de0891c"
149
149
  }