docusaurus-theme-openapi-docs 0.0.0-1074 → 0.0.0-1075
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/theme/ApiExplorer/Request/index.js +56 -11
- package/lib/theme/ApiExplorer/Request/makeRequest.d.ts +7 -1
- package/lib/theme/ApiExplorer/Request/makeRequest.js +94 -24
- package/lib/theme/translationIds.d.ts +4 -0
- package/lib/theme/translationIds.js +4 -0
- package/lib/types.d.ts +2 -0
- package/package.json +3 -3
- package/src/theme/ApiExplorer/Request/index.tsx +58 -11
- package/src/theme/ApiExplorer/Request/makeRequest.ts +106 -25
- package/src/theme/translationIds.ts +4 -0
- package/src/types.ts +2 -0
|
@@ -71,6 +71,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
71
71
|
const react_1 = __importStar(require("react"));
|
|
72
72
|
const client_1 = require("@docusaurus/plugin-content-docs/client");
|
|
73
73
|
const Translate_1 = require("@docusaurus/Translate");
|
|
74
|
+
const useDocusaurusContext_1 = __importDefault(
|
|
75
|
+
require("@docusaurus/useDocusaurusContext")
|
|
76
|
+
);
|
|
74
77
|
const Accept_1 = __importDefault(require("@theme/ApiExplorer/Accept"));
|
|
75
78
|
const Authorization_1 = __importDefault(
|
|
76
79
|
require("@theme/ApiExplorer/Authorization")
|
|
@@ -91,11 +94,17 @@ const hooks_1 = require("@theme/ApiItem/hooks");
|
|
|
91
94
|
const translationIds_1 = require("@theme/translationIds");
|
|
92
95
|
const sdk = __importStar(require("postman-collection"));
|
|
93
96
|
const react_hook_form_1 = require("react-hook-form");
|
|
94
|
-
const makeRequest_1 =
|
|
97
|
+
const makeRequest_1 = __importStar(require("./makeRequest"));
|
|
95
98
|
function Request({ item }) {
|
|
96
99
|
const postman = new sdk.Request(item.postman);
|
|
97
100
|
const metadata = (0, client_1.useDoc)();
|
|
98
|
-
const { proxy, hide_send_button: hideSendButton } =
|
|
101
|
+
const { proxy: frontMatterProxy, hide_send_button: hideSendButton } =
|
|
102
|
+
metadata.frontMatter;
|
|
103
|
+
const { siteConfig } = (0, useDocusaurusContext_1.default)();
|
|
104
|
+
const themeConfig = siteConfig.themeConfig;
|
|
105
|
+
const requestTimeout = themeConfig.api?.requestTimeout;
|
|
106
|
+
// Frontmatter proxy (per-spec) takes precedence over theme config proxy (site-wide)
|
|
107
|
+
const proxy = frontMatterProxy ?? themeConfig.api?.proxy;
|
|
99
108
|
const pathParams = (0, hooks_1.useTypedSelector)(
|
|
100
109
|
(state) => state.params.path
|
|
101
110
|
);
|
|
@@ -180,6 +189,35 @@ function Request({ item }) {
|
|
|
180
189
|
res.headers &&
|
|
181
190
|
dispatch((0, slice_1.setHeaders)(Object.fromEntries(res.headers)));
|
|
182
191
|
};
|
|
192
|
+
const getErrorMessage = (errorType) => {
|
|
193
|
+
switch (errorType) {
|
|
194
|
+
case "timeout":
|
|
195
|
+
return (0, Translate_1.translate)({
|
|
196
|
+
id: translationIds_1.OPENAPI_REQUEST.ERROR_TIMEOUT,
|
|
197
|
+
message:
|
|
198
|
+
"The request timed out waiting for the server to respond. Please try again. If the issue persists, try using a different client (e.g., curl) with a longer timeout.",
|
|
199
|
+
});
|
|
200
|
+
case "network":
|
|
201
|
+
return (0, Translate_1.translate)({
|
|
202
|
+
id: translationIds_1.OPENAPI_REQUEST.ERROR_NETWORK,
|
|
203
|
+
message:
|
|
204
|
+
"Unable to reach the server. Please check your network connection and verify the server URL is correct. If the server is running, this may be a CORS issue.",
|
|
205
|
+
});
|
|
206
|
+
case "cors":
|
|
207
|
+
return (0, Translate_1.translate)({
|
|
208
|
+
id: translationIds_1.OPENAPI_REQUEST.ERROR_CORS,
|
|
209
|
+
message:
|
|
210
|
+
"The request was blocked, possibly due to CORS restrictions. Ensure the server allows requests from this origin, or try using a proxy.",
|
|
211
|
+
});
|
|
212
|
+
case "unknown":
|
|
213
|
+
default:
|
|
214
|
+
return (0, Translate_1.translate)({
|
|
215
|
+
id: translationIds_1.OPENAPI_REQUEST.ERROR_UNKNOWN,
|
|
216
|
+
message:
|
|
217
|
+
"An unexpected error occurred while making the request. Please try again.",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
};
|
|
183
221
|
const onSubmit = async (data) => {
|
|
184
222
|
dispatch(
|
|
185
223
|
(0, slice_1.setResponse)(
|
|
@@ -191,7 +229,12 @@ function Request({ item }) {
|
|
|
191
229
|
);
|
|
192
230
|
try {
|
|
193
231
|
await delay(1200);
|
|
194
|
-
const res = await (0, makeRequest_1.default)(
|
|
232
|
+
const res = await (0, makeRequest_1.default)(
|
|
233
|
+
postmanRequest,
|
|
234
|
+
proxy,
|
|
235
|
+
body,
|
|
236
|
+
requestTimeout
|
|
237
|
+
);
|
|
195
238
|
if (res.headers.get("content-type")?.includes("text/event-stream")) {
|
|
196
239
|
await handleEventStream(res);
|
|
197
240
|
} else {
|
|
@@ -199,14 +242,16 @@ function Request({ item }) {
|
|
|
199
242
|
}
|
|
200
243
|
} catch (e) {
|
|
201
244
|
console.log(e);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
let errorMessage;
|
|
246
|
+
if (e instanceof makeRequest_1.RequestError) {
|
|
247
|
+
errorMessage = getErrorMessage(e.type);
|
|
248
|
+
} else {
|
|
249
|
+
errorMessage = (0, Translate_1.translate)({
|
|
250
|
+
id: translationIds_1.OPENAPI_REQUEST.CONNECTION_FAILED,
|
|
251
|
+
message: "Connection failed",
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
dispatch((0, slice_1.setResponse)(errorMessage));
|
|
210
255
|
dispatch((0, slice_1.clearCode)());
|
|
211
256
|
dispatch((0, slice_1.clearHeaders)());
|
|
212
257
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { Body } from "@theme/ApiExplorer/Body/slice";
|
|
2
2
|
import * as sdk from "postman-collection";
|
|
3
|
-
|
|
3
|
+
export type RequestErrorType = "timeout" | "network" | "cors" | "abort" | "unknown";
|
|
4
|
+
export declare class RequestError extends Error {
|
|
5
|
+
type: RequestErrorType;
|
|
6
|
+
originalError?: Error;
|
|
7
|
+
constructor(type: RequestErrorType, message: string, originalError?: Error);
|
|
8
|
+
}
|
|
9
|
+
declare function makeRequest(request: sdk.Request, proxy: string | undefined, _body: Body, timeout?: number): Promise<Response>;
|
|
4
10
|
export default makeRequest;
|
|
@@ -6,13 +6,65 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
* ========================================================================== */
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
)
|
|
15
|
-
|
|
9
|
+
exports.RequestError = void 0;
|
|
10
|
+
class RequestError extends Error {
|
|
11
|
+
type;
|
|
12
|
+
originalError;
|
|
13
|
+
constructor(type, message, originalError) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "RequestError";
|
|
16
|
+
this.type = type;
|
|
17
|
+
this.originalError = originalError;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.RequestError = RequestError;
|
|
21
|
+
const DEFAULT_REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
22
|
+
function fetchWithtimeout(url, options, timeout = DEFAULT_REQUEST_TIMEOUT) {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
25
|
+
return fetch(url, {
|
|
26
|
+
...options,
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
})
|
|
29
|
+
.then((response) => {
|
|
30
|
+
clearTimeout(timeoutId);
|
|
31
|
+
return response;
|
|
32
|
+
})
|
|
33
|
+
.catch((error) => {
|
|
34
|
+
clearTimeout(timeoutId);
|
|
35
|
+
// Check if it was an abort due to timeout
|
|
36
|
+
if (error.name === "AbortError") {
|
|
37
|
+
throw new RequestError(
|
|
38
|
+
"timeout",
|
|
39
|
+
"The request timed out waiting for the server to respond. Please try again. If the issue persists, try using a different client (e.g., curl) with a longer timeout.",
|
|
40
|
+
error
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
// Check for network errors (offline, DNS failure, etc.)
|
|
44
|
+
if (error instanceof TypeError && error.message === "Failed to fetch") {
|
|
45
|
+
// This could be CORS, network failure, or the server being unreachable
|
|
46
|
+
throw new RequestError(
|
|
47
|
+
"network",
|
|
48
|
+
"Unable to reach the server. Please check your network connection and verify the server URL is correct. If the server is running, this may be a CORS issue.",
|
|
49
|
+
error
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
// Handle other TypeErrors that might indicate CORS issues
|
|
53
|
+
if (error instanceof TypeError) {
|
|
54
|
+
throw new RequestError(
|
|
55
|
+
"cors",
|
|
56
|
+
"The request was blocked, possibly due to CORS restrictions. Ensure the server allows requests from this origin, or try using a proxy.",
|
|
57
|
+
error
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
// Generic error fallback
|
|
61
|
+
throw new RequestError(
|
|
62
|
+
"unknown",
|
|
63
|
+
error.message ||
|
|
64
|
+
"An unexpected error occurred while making the request.",
|
|
65
|
+
error
|
|
66
|
+
);
|
|
67
|
+
});
|
|
16
68
|
}
|
|
17
69
|
async function loadImage(content) {
|
|
18
70
|
return new Promise((accept, reject) => {
|
|
@@ -33,7 +85,12 @@ async function loadImage(content) {
|
|
|
33
85
|
reader.readAsArrayBuffer(content);
|
|
34
86
|
});
|
|
35
87
|
}
|
|
36
|
-
async function makeRequest(
|
|
88
|
+
async function makeRequest(
|
|
89
|
+
request,
|
|
90
|
+
proxy,
|
|
91
|
+
_body,
|
|
92
|
+
timeout = DEFAULT_REQUEST_TIMEOUT
|
|
93
|
+
) {
|
|
37
94
|
const headers = request.toJSON().header;
|
|
38
95
|
let myHeaders = new Headers();
|
|
39
96
|
if (headers) {
|
|
@@ -171,7 +228,8 @@ async function makeRequest(request, proxy, _body) {
|
|
|
171
228
|
let normalizedProxy = proxy.replace(/\/$/, "") + "/";
|
|
172
229
|
finalUrl = normalizedProxy + request.url.toString();
|
|
173
230
|
}
|
|
174
|
-
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetchWithtimeout(finalUrl, requestOptions, timeout);
|
|
175
233
|
const contentType = response.headers.get("content-type");
|
|
176
234
|
let fileExtension = "";
|
|
177
235
|
if (contentType) {
|
|
@@ -199,25 +257,37 @@ async function makeRequest(request, proxy, _body) {
|
|
|
199
257
|
fileExtension = ".zip";
|
|
200
258
|
}
|
|
201
259
|
if (fileExtension) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
260
|
+
const blob = await response.blob();
|
|
261
|
+
const url = window.URL.createObjectURL(blob);
|
|
262
|
+
const link = document.createElement("a");
|
|
263
|
+
link.href = url;
|
|
264
|
+
// Now the file name includes the extension
|
|
265
|
+
link.setAttribute("download", `file${fileExtension}`);
|
|
266
|
+
// These two lines are necessary to make the link click in Firefox
|
|
267
|
+
link.style.display = "none";
|
|
268
|
+
document.body.appendChild(link);
|
|
269
|
+
link.click();
|
|
270
|
+
// After link is clicked, it's safe to remove it.
|
|
271
|
+
setTimeout(() => document.body.removeChild(link), 0);
|
|
272
|
+
return response;
|
|
216
273
|
} else {
|
|
217
274
|
return response;
|
|
218
275
|
}
|
|
219
276
|
}
|
|
220
277
|
return response;
|
|
221
|
-
})
|
|
278
|
+
} catch (error) {
|
|
279
|
+
// Re-throw RequestError instances as-is
|
|
280
|
+
if (error instanceof RequestError) {
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
// Wrap unexpected errors
|
|
284
|
+
throw new RequestError(
|
|
285
|
+
"unknown",
|
|
286
|
+
error instanceof Error
|
|
287
|
+
? error.message
|
|
288
|
+
: "An unexpected error occurred while processing the response.",
|
|
289
|
+
error instanceof Error ? error : undefined
|
|
290
|
+
);
|
|
291
|
+
}
|
|
222
292
|
}
|
|
223
293
|
exports.default = makeRequest;
|
|
@@ -20,6 +20,10 @@ export declare const OPENAPI_REQUEST: {
|
|
|
20
20
|
PARAMETERS_TITLE: string;
|
|
21
21
|
FETCHING_MESSAGE: string;
|
|
22
22
|
CONNECTION_FAILED: string;
|
|
23
|
+
ERROR_TIMEOUT: string;
|
|
24
|
+
ERROR_NETWORK: string;
|
|
25
|
+
ERROR_CORS: string;
|
|
26
|
+
ERROR_UNKNOWN: string;
|
|
23
27
|
};
|
|
24
28
|
export declare const OPENAPI_SERVER: {
|
|
25
29
|
EDIT_BUTTON: string;
|
|
@@ -43,6 +43,10 @@ exports.OPENAPI_REQUEST = {
|
|
|
43
43
|
PARAMETERS_TITLE: "theme.openapi.request.parameters.title",
|
|
44
44
|
FETCHING_MESSAGE: "theme.openapi.request.fetchingMessage",
|
|
45
45
|
CONNECTION_FAILED: "theme.openapi.request.connectionFailed",
|
|
46
|
+
ERROR_TIMEOUT: "theme.openapi.request.error.timeout",
|
|
47
|
+
ERROR_NETWORK: "theme.openapi.request.error.network",
|
|
48
|
+
ERROR_CORS: "theme.openapi.request.error.cors",
|
|
49
|
+
ERROR_UNKNOWN: "theme.openapi.request.error.unknown",
|
|
46
50
|
};
|
|
47
51
|
exports.OPENAPI_SERVER = {
|
|
48
52
|
EDIT_BUTTON: "theme.openapi.server.editButton",
|
package/lib/types.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export interface ThemeConfig {
|
|
|
4
4
|
api?: {
|
|
5
5
|
proxy?: string;
|
|
6
6
|
authPersistance?: false | "localStorage" | "sessionStorage";
|
|
7
|
+
/** Request timeout in milliseconds. Defaults to 30000 (30 seconds). */
|
|
8
|
+
requestTimeout?: number;
|
|
7
9
|
};
|
|
8
10
|
}
|
|
9
11
|
export type JSONSchema = JSONSchema4 | JSONSchema6 | JSONSchema7;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docusaurus-theme-openapi-docs",
|
|
3
3
|
"description": "OpenAPI theme for Docusaurus.",
|
|
4
|
-
"version": "0.0.0-
|
|
4
|
+
"version": "0.0.0-1075",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"openapi",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@types/postman-collection": "^3.5.11",
|
|
39
39
|
"@types/react-modal": "^3.16.3",
|
|
40
40
|
"concurrently": "^9.2.0",
|
|
41
|
-
"docusaurus-plugin-openapi-docs": "0.0.0-
|
|
41
|
+
"docusaurus-plugin-openapi-docs": "0.0.0-1075",
|
|
42
42
|
"docusaurus-plugin-sass": "^0.2.6",
|
|
43
43
|
"eslint-plugin-prettier": "^5.5.1"
|
|
44
44
|
},
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": ">=14"
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "d4ec26e753c5bab96246a39a07b4ccd5a5ae8344"
|
|
85
85
|
}
|
|
@@ -10,6 +10,8 @@ import React, { useState } from "react";
|
|
|
10
10
|
|
|
11
11
|
import { useDoc } from "@docusaurus/plugin-content-docs/client";
|
|
12
12
|
import { translate } from "@docusaurus/Translate";
|
|
13
|
+
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
|
14
|
+
import type { ThemeConfig } from "docusaurus-theme-openapi-docs/src/types";
|
|
13
15
|
import Accept from "@theme/ApiExplorer/Accept";
|
|
14
16
|
import Authorization from "@theme/ApiExplorer/Authorization";
|
|
15
17
|
import Body from "@theme/ApiExplorer/Body";
|
|
@@ -31,12 +33,18 @@ import { ApiItem } from "docusaurus-plugin-openapi-docs/src/types";
|
|
|
31
33
|
import * as sdk from "postman-collection";
|
|
32
34
|
import { FormProvider, useForm } from "react-hook-form";
|
|
33
35
|
|
|
34
|
-
import makeRequest from "./makeRequest";
|
|
36
|
+
import makeRequest, { RequestError, RequestErrorType } from "./makeRequest";
|
|
35
37
|
|
|
36
38
|
function Request({ item }: { item: ApiItem }) {
|
|
37
39
|
const postman = new sdk.Request(item.postman);
|
|
38
40
|
const metadata = useDoc();
|
|
39
|
-
const { proxy, hide_send_button: hideSendButton } =
|
|
41
|
+
const { proxy: frontMatterProxy, hide_send_button: hideSendButton } =
|
|
42
|
+
metadata.frontMatter;
|
|
43
|
+
const { siteConfig } = useDocusaurusContext();
|
|
44
|
+
const themeConfig = siteConfig.themeConfig as ThemeConfig;
|
|
45
|
+
const requestTimeout = themeConfig.api?.requestTimeout;
|
|
46
|
+
// Frontmatter proxy (per-spec) takes precedence over theme config proxy (site-wide)
|
|
47
|
+
const proxy = frontMatterProxy ?? themeConfig.api?.proxy;
|
|
40
48
|
|
|
41
49
|
const pathParams = useTypedSelector((state: any) => state.params.path);
|
|
42
50
|
const queryParams = useTypedSelector((state: any) => state.params.query);
|
|
@@ -118,6 +126,36 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
118
126
|
res.headers && dispatch(setHeaders(Object.fromEntries(res.headers)));
|
|
119
127
|
};
|
|
120
128
|
|
|
129
|
+
const getErrorMessage = (errorType: RequestErrorType): string => {
|
|
130
|
+
switch (errorType) {
|
|
131
|
+
case "timeout":
|
|
132
|
+
return translate({
|
|
133
|
+
id: OPENAPI_REQUEST.ERROR_TIMEOUT,
|
|
134
|
+
message:
|
|
135
|
+
"The request timed out waiting for the server to respond. Please try again. If the issue persists, try using a different client (e.g., curl) with a longer timeout.",
|
|
136
|
+
});
|
|
137
|
+
case "network":
|
|
138
|
+
return translate({
|
|
139
|
+
id: OPENAPI_REQUEST.ERROR_NETWORK,
|
|
140
|
+
message:
|
|
141
|
+
"Unable to reach the server. Please check your network connection and verify the server URL is correct. If the server is running, this may be a CORS issue.",
|
|
142
|
+
});
|
|
143
|
+
case "cors":
|
|
144
|
+
return translate({
|
|
145
|
+
id: OPENAPI_REQUEST.ERROR_CORS,
|
|
146
|
+
message:
|
|
147
|
+
"The request was blocked, possibly due to CORS restrictions. Ensure the server allows requests from this origin, or try using a proxy.",
|
|
148
|
+
});
|
|
149
|
+
case "unknown":
|
|
150
|
+
default:
|
|
151
|
+
return translate({
|
|
152
|
+
id: OPENAPI_REQUEST.ERROR_UNKNOWN,
|
|
153
|
+
message:
|
|
154
|
+
"An unexpected error occurred while making the request. Please try again.",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
121
159
|
const onSubmit = async (data) => {
|
|
122
160
|
dispatch(
|
|
123
161
|
setResponse(
|
|
@@ -129,7 +167,12 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
129
167
|
);
|
|
130
168
|
try {
|
|
131
169
|
await delay(1200);
|
|
132
|
-
const res = await makeRequest(
|
|
170
|
+
const res = await makeRequest(
|
|
171
|
+
postmanRequest,
|
|
172
|
+
proxy,
|
|
173
|
+
body,
|
|
174
|
+
requestTimeout
|
|
175
|
+
);
|
|
133
176
|
if (res.headers.get("content-type")?.includes("text/event-stream")) {
|
|
134
177
|
await handleEventStream(res);
|
|
135
178
|
} else {
|
|
@@ -137,14 +180,18 @@ function Request({ item }: { item: ApiItem }) {
|
|
|
137
180
|
}
|
|
138
181
|
} catch (e) {
|
|
139
182
|
console.log(e);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
183
|
+
|
|
184
|
+
let errorMessage: string;
|
|
185
|
+
if (e instanceof RequestError) {
|
|
186
|
+
errorMessage = getErrorMessage(e.type);
|
|
187
|
+
} else {
|
|
188
|
+
errorMessage = translate({
|
|
189
|
+
id: OPENAPI_REQUEST.CONNECTION_FAILED,
|
|
190
|
+
message: "Connection failed",
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
dispatch(setResponse(errorMessage));
|
|
148
195
|
dispatch(clearCode());
|
|
149
196
|
dispatch(clearHeaders());
|
|
150
197
|
}
|
|
@@ -8,17 +8,83 @@
|
|
|
8
8
|
import { Body } from "@theme/ApiExplorer/Body/slice";
|
|
9
9
|
import * as sdk from "postman-collection";
|
|
10
10
|
|
|
11
|
+
// Custom error types for better error handling
|
|
12
|
+
export type RequestErrorType =
|
|
13
|
+
| "timeout"
|
|
14
|
+
| "network"
|
|
15
|
+
| "cors"
|
|
16
|
+
| "abort"
|
|
17
|
+
| "unknown";
|
|
18
|
+
|
|
19
|
+
export class RequestError extends Error {
|
|
20
|
+
type: RequestErrorType;
|
|
21
|
+
originalError?: Error;
|
|
22
|
+
|
|
23
|
+
constructor(type: RequestErrorType, message: string, originalError?: Error) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "RequestError";
|
|
26
|
+
this.type = type;
|
|
27
|
+
this.originalError = originalError;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_REQUEST_TIMEOUT = 30000; // 30 seconds
|
|
32
|
+
|
|
11
33
|
function fetchWithtimeout(
|
|
12
34
|
url: string,
|
|
13
35
|
options: RequestInit,
|
|
14
|
-
timeout =
|
|
15
|
-
):
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
timeout = DEFAULT_REQUEST_TIMEOUT
|
|
37
|
+
): Promise<Response> {
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
40
|
+
|
|
41
|
+
return fetch(url, {
|
|
42
|
+
...options,
|
|
43
|
+
signal: controller.signal,
|
|
44
|
+
})
|
|
45
|
+
.then((response) => {
|
|
46
|
+
clearTimeout(timeoutId);
|
|
47
|
+
return response;
|
|
48
|
+
})
|
|
49
|
+
.catch((error) => {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
|
|
52
|
+
// Check if it was an abort due to timeout
|
|
53
|
+
if (error.name === "AbortError") {
|
|
54
|
+
throw new RequestError(
|
|
55
|
+
"timeout",
|
|
56
|
+
"The request timed out waiting for the server to respond. Please try again. If the issue persists, try using a different client (e.g., curl) with a longer timeout.",
|
|
57
|
+
error
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for network errors (offline, DNS failure, etc.)
|
|
62
|
+
if (error instanceof TypeError && error.message === "Failed to fetch") {
|
|
63
|
+
// This could be CORS, network failure, or the server being unreachable
|
|
64
|
+
throw new RequestError(
|
|
65
|
+
"network",
|
|
66
|
+
"Unable to reach the server. Please check your network connection and verify the server URL is correct. If the server is running, this may be a CORS issue.",
|
|
67
|
+
error
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle other TypeErrors that might indicate CORS issues
|
|
72
|
+
if (error instanceof TypeError) {
|
|
73
|
+
throw new RequestError(
|
|
74
|
+
"cors",
|
|
75
|
+
"The request was blocked, possibly due to CORS restrictions. Ensure the server allows requests from this origin, or try using a proxy.",
|
|
76
|
+
error
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generic error fallback
|
|
81
|
+
throw new RequestError(
|
|
82
|
+
"unknown",
|
|
83
|
+
error.message ||
|
|
84
|
+
"An unexpected error occurred while making the request.",
|
|
85
|
+
error
|
|
86
|
+
);
|
|
87
|
+
});
|
|
22
88
|
}
|
|
23
89
|
|
|
24
90
|
async function loadImage(content: Blob): Promise<string | ArrayBuffer | null> {
|
|
@@ -47,7 +113,8 @@ async function loadImage(content: Blob): Promise<string | ArrayBuffer | null> {
|
|
|
47
113
|
async function makeRequest(
|
|
48
114
|
request: sdk.Request,
|
|
49
115
|
proxy: string | undefined,
|
|
50
|
-
_body: Body
|
|
116
|
+
_body: Body,
|
|
117
|
+
timeout: number = DEFAULT_REQUEST_TIMEOUT
|
|
51
118
|
) {
|
|
52
119
|
const headers = request.toJSON().header;
|
|
53
120
|
|
|
@@ -194,7 +261,8 @@ async function makeRequest(
|
|
|
194
261
|
finalUrl = normalizedProxy + request.url.toString();
|
|
195
262
|
}
|
|
196
263
|
|
|
197
|
-
|
|
264
|
+
try {
|
|
265
|
+
const response = await fetchWithtimeout(finalUrl, requestOptions, timeout);
|
|
198
266
|
const contentType = response.headers.get("content-type");
|
|
199
267
|
let fileExtension = "";
|
|
200
268
|
|
|
@@ -224,32 +292,45 @@ async function makeRequest(
|
|
|
224
292
|
}
|
|
225
293
|
|
|
226
294
|
if (fileExtension) {
|
|
227
|
-
|
|
228
|
-
|
|
295
|
+
const blob = await response.blob();
|
|
296
|
+
const url = window.URL.createObjectURL(blob);
|
|
229
297
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
298
|
+
const link = document.createElement("a");
|
|
299
|
+
link.href = url;
|
|
300
|
+
// Now the file name includes the extension
|
|
301
|
+
link.setAttribute("download", `file${fileExtension}`);
|
|
234
302
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
303
|
+
// These two lines are necessary to make the link click in Firefox
|
|
304
|
+
link.style.display = "none";
|
|
305
|
+
document.body.appendChild(link);
|
|
238
306
|
|
|
239
|
-
|
|
307
|
+
link.click();
|
|
240
308
|
|
|
241
|
-
|
|
242
|
-
|
|
309
|
+
// After link is clicked, it's safe to remove it.
|
|
310
|
+
setTimeout(() => document.body.removeChild(link), 0);
|
|
243
311
|
|
|
244
|
-
|
|
245
|
-
});
|
|
312
|
+
return response;
|
|
246
313
|
} else {
|
|
247
314
|
return response;
|
|
248
315
|
}
|
|
249
316
|
}
|
|
250
317
|
|
|
251
318
|
return response;
|
|
252
|
-
})
|
|
319
|
+
} catch (error) {
|
|
320
|
+
// Re-throw RequestError instances as-is
|
|
321
|
+
if (error instanceof RequestError) {
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Wrap unexpected errors
|
|
326
|
+
throw new RequestError(
|
|
327
|
+
"unknown",
|
|
328
|
+
error instanceof Error
|
|
329
|
+
? error.message
|
|
330
|
+
: "An unexpected error occurred while processing the response.",
|
|
331
|
+
error instanceof Error ? error : undefined
|
|
332
|
+
);
|
|
333
|
+
}
|
|
253
334
|
}
|
|
254
335
|
|
|
255
336
|
export default makeRequest;
|
|
@@ -29,6 +29,10 @@ export const OPENAPI_REQUEST = {
|
|
|
29
29
|
PARAMETERS_TITLE: "theme.openapi.request.parameters.title",
|
|
30
30
|
FETCHING_MESSAGE: "theme.openapi.request.fetchingMessage",
|
|
31
31
|
CONNECTION_FAILED: "theme.openapi.request.connectionFailed",
|
|
32
|
+
ERROR_TIMEOUT: "theme.openapi.request.error.timeout",
|
|
33
|
+
ERROR_NETWORK: "theme.openapi.request.error.network",
|
|
34
|
+
ERROR_CORS: "theme.openapi.request.error.cors",
|
|
35
|
+
ERROR_UNKNOWN: "theme.openapi.request.error.unknown",
|
|
32
36
|
};
|
|
33
37
|
|
|
34
38
|
export const OPENAPI_SERVER = {
|
package/src/types.ts
CHANGED