@schukai/monster 3.110.0 → 3.110.2
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/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/source/components/datatable/status.mjs +179 -168
- package/source/components/form/reload.mjs +10 -4
- package/source/components/form/util/fetch.mjs +81 -88
- package/source/data/datasource/server/restapi/data-fetch-error.mjs +20 -1
- package/source/data/datasource/server/restapi.mjs +2 -1
- package/source/i18n/internal.mjs +1 -1
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
|
3
3
|
|
4
4
|
|
5
|
+
## [3.110.2] - 2025-02-19
|
6
|
+
|
7
|
+
### Bug Fixes
|
8
|
+
|
9
|
+
- **reload:** set shadowRoot to false
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
## [3.110.1] - 2025-02-19
|
14
|
+
|
15
|
+
### Bug Fixes
|
16
|
+
|
17
|
+
- optimize rest errors
|
18
|
+
|
19
|
+
|
20
|
+
|
5
21
|
## [3.110.0] - 2025-02-18
|
6
22
|
|
7
23
|
### Add Features
|
package/package.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8","buffer":"^6.0.3"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.110.
|
1
|
+
{"author":"schukai GmbH","dependencies":{"@floating-ui/dom":"^1.6.13","@popperjs/core":"^2.11.8","buffer":"^6.0.3"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"3.110.2"}
|
@@ -13,26 +13,26 @@
|
|
13
13
|
*/
|
14
14
|
|
15
15
|
import {
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
assembleMethodSymbol,
|
17
|
+
CustomElement,
|
18
|
+
registerCustomElement,
|
19
19
|
} from "../../dom/customelement.mjs";
|
20
|
-
import {
|
21
|
-
import {
|
22
|
-
import {
|
23
|
-
import {
|
24
|
-
import {
|
25
|
-
import {
|
20
|
+
import {findElementWithSelectorUpwards} from "../../dom/util.mjs";
|
21
|
+
import {ThemeStyleSheet} from "../stylesheet/theme.mjs";
|
22
|
+
import {Datasource} from "./datasource.mjs";
|
23
|
+
import {SpinnerStyleSheet} from "../stylesheet/spinner.mjs";
|
24
|
+
import {isString} from "../../types/is.mjs";
|
25
|
+
import {instanceSymbol} from "../../constants.mjs";
|
26
26
|
import "../form/select.mjs";
|
27
27
|
|
28
28
|
import "./datasource/dom.mjs";
|
29
29
|
import "./datasource/rest.mjs";
|
30
30
|
import "../form/popper.mjs";
|
31
31
|
import "../form/context-error.mjs";
|
32
|
-
import {
|
32
|
+
import {StatusStyleSheet} from "./stylesheet/status.mjs";
|
33
33
|
import {Formatter} from "../../text/formatter.mjs";
|
34
34
|
|
35
|
-
export {
|
35
|
+
export {DatasourceStatus};
|
36
36
|
|
37
37
|
/**
|
38
38
|
* @private
|
@@ -59,86 +59,97 @@ const datasourceLinkedElementSymbol = Symbol("datasourceLinkedElement");
|
|
59
59
|
* @summary The Status component is used to show the current status of a datasource.
|
60
60
|
*/
|
61
61
|
class DatasourceStatus extends CustomElement {
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
62
|
+
/**
|
63
|
+
*/
|
64
|
+
constructor() {
|
65
|
+
super();
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* This method is called by the `instanceof` operator.
|
70
|
+
* @return {symbol}
|
71
|
+
*/
|
72
|
+
static get [instanceSymbol]() {
|
73
|
+
return Symbol.for("@schukai/monster/components/datatable/status@@instance");
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* To set the options via the HTML tag, the attribute `data-monster-options` must be used.
|
78
|
+
* @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
|
79
|
+
*
|
80
|
+
* The individual configuration values can be found in the table.
|
81
|
+
*
|
82
|
+
* @property {Object} templates Template definitions
|
83
|
+
* @property {string} templates.main Main template
|
84
|
+
* @property {Object} datasource Datasource configuration
|
85
|
+
* @property {string} datasource.selector The selector of the datasource
|
86
|
+
* @property {Object} callbacks Callbacks
|
87
|
+
* @property {Function} callbacks.onError Callback function for error handling <code>function(message: string, event: Event): string</code>
|
88
|
+
* @property {Object} timeouts Timeouts
|
89
|
+
* @property {number} timeouts.message Timeout for the message
|
90
|
+
* @property {Object} state State
|
91
|
+
*/
|
92
|
+
get defaults() {
|
93
|
+
return Object.assign({}, super.defaults, {
|
94
|
+
templates: {
|
95
|
+
main: getTemplate(),
|
96
|
+
},
|
97
|
+
|
98
|
+
datasource: {
|
99
|
+
selector: null,
|
100
|
+
},
|
101
|
+
|
102
|
+
callbacks: {
|
103
|
+
onError: null
|
104
|
+
},
|
105
|
+
|
106
|
+
|
107
|
+
timeouts: {
|
108
|
+
message: 4000,
|
109
|
+
},
|
110
|
+
|
111
|
+
state: {
|
112
|
+
spinner: "hide",
|
113
|
+
},
|
114
|
+
});
|
115
|
+
}
|
116
|
+
|
117
|
+
/**
|
118
|
+
*
|
119
|
+
* @return {string}
|
120
|
+
*/
|
121
|
+
static getTag() {
|
122
|
+
return "monster-datasource-status";
|
123
|
+
}
|
124
|
+
|
125
|
+
/**
|
126
|
+
* @private
|
127
|
+
*/
|
128
|
+
[assembleMethodSymbol]() {
|
129
|
+
super[assembleMethodSymbol]();
|
130
|
+
|
131
|
+
initControlReferences.call(this);
|
132
|
+
initEventHandler.call(this);
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
*
|
137
|
+
* @param message
|
138
|
+
* @param timeout
|
139
|
+
* @returns {DatasourceStatus}
|
140
|
+
*/
|
141
|
+
setErrorMessage(message, timeout) {
|
142
|
+
this[errorElementSymbol].setErrorMessage(message, timeout);
|
143
|
+
return this;
|
144
|
+
}
|
145
|
+
|
146
|
+
/**
|
147
|
+
*
|
148
|
+
* @return [CSSStyleSheet]
|
149
|
+
*/
|
150
|
+
static getCSSStyleSheet() {
|
151
|
+
return [StatusStyleSheet, SpinnerStyleSheet, ThemeStyleSheet];
|
152
|
+
}
|
142
153
|
}
|
143
154
|
|
144
155
|
/**
|
@@ -147,85 +158,85 @@ class DatasourceStatus extends CustomElement {
|
|
147
158
|
* @throws {Error} no shadow-root is defined
|
148
159
|
*/
|
149
160
|
function initControlReferences() {
|
150
|
-
|
151
|
-
|
152
|
-
|
161
|
+
if (!this.shadowRoot) {
|
162
|
+
throw new Error("no shadow-root is defined");
|
163
|
+
}
|
153
164
|
|
154
|
-
|
155
|
-
|
156
|
-
|
165
|
+
this[errorElementSymbol] = this.shadowRoot.querySelector(
|
166
|
+
"monster-context-error",
|
167
|
+
);
|
157
168
|
}
|
158
169
|
|
159
170
|
/**
|
160
171
|
* @private
|
161
172
|
*/
|
162
173
|
function initEventHandler() {
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
174
|
+
const selector = this.getOption("datasource.selector", "");
|
175
|
+
const self = this;
|
176
|
+
|
177
|
+
if (isString(selector)) {
|
178
|
+
const element = findElementWithSelectorUpwards(this, selector);
|
179
|
+
if (element === null) {
|
180
|
+
throw new Error("the selector must match exactly one element");
|
181
|
+
}
|
182
|
+
|
183
|
+
if (!(element instanceof Datasource)) {
|
184
|
+
throw new TypeError("the element must be a datasource");
|
185
|
+
}
|
186
|
+
|
187
|
+
let fadeOutTimer = null;
|
188
|
+
|
189
|
+
this[datasourceLinkedElementSymbol] = element;
|
190
|
+
element.addEventListener("monster-datasource-fetched", function () {
|
191
|
+
fadeOutTimer = setTimeout(() => {
|
192
|
+
self.setOption("state.spinner", "hide");
|
193
|
+
}, 800);
|
194
|
+
});
|
195
|
+
|
196
|
+
element.addEventListener("monster-datasource-fetch", function () {
|
197
|
+
if (fadeOutTimer) {
|
198
|
+
clearTimeout(fadeOutTimer);
|
199
|
+
fadeOutTimer = null;
|
200
|
+
}
|
201
|
+
|
202
|
+
self.setOption("state.spinner", "show");
|
203
|
+
});
|
204
|
+
|
205
|
+
element.addEventListener("monster-datasource-error", function (event) {
|
206
|
+
if (fadeOutTimer) {
|
207
|
+
clearTimeout(fadeOutTimer);
|
208
|
+
fadeOutTimer = null;
|
209
|
+
}
|
210
|
+
|
211
|
+
self.setOption("state.spinner", "hide");
|
212
|
+
|
213
|
+
const timeout = self.getOption("timeouts.message", 4000);
|
214
|
+
let msg = "Cannot load data";
|
215
|
+
|
216
|
+
try {
|
217
|
+
if (event.detail.error instanceof Error) {
|
218
|
+
msg = event.detail.error.message;
|
219
|
+
} else if (event.detail.error instanceof Object) {
|
220
|
+
msg = JSON.stringify(event.detail.error);
|
221
|
+
} else if (event.detail.error instanceof String) {
|
222
|
+
msg = event.detail.error;
|
223
|
+
} else if (event.detail.error instanceof Number) {
|
224
|
+
msg = event.detail.error.toString();
|
225
|
+
} else {
|
226
|
+
msg = event.detail.error;
|
227
|
+
}
|
228
|
+
} catch (e) {
|
229
|
+
} finally {
|
230
|
+
|
231
|
+
const callback = self.getOption("callbacks.onError", null);
|
232
|
+
if (callback) {
|
233
|
+
callback.call(self, msg, event);
|
234
|
+
} else {
|
235
|
+
self[errorElementSymbol].setErrorMessage(msg, timeout);
|
236
|
+
}
|
237
|
+
}
|
238
|
+
});
|
239
|
+
}
|
229
240
|
}
|
230
241
|
|
231
242
|
/**
|
@@ -233,13 +244,13 @@ function initEventHandler() {
|
|
233
244
|
* @return {string}
|
234
245
|
*/
|
235
246
|
function getTemplate() {
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
247
|
+
// language=HTML
|
248
|
+
return `
|
249
|
+
<div data-monster-role="control" part="control"
|
250
|
+
data-monster-attributes="disabled path:disabled | if:true">
|
240
251
|
<monster-context-error
|
241
252
|
data-monster-option-classes-button="monster-theme-error-2 monster-theme-background-inherit"></monster-context-error>
|
242
|
-
<div class="monster-spinner"
|
253
|
+
<div class="monster-spinner"
|
243
254
|
data-monster-attributes="data-monster-state-loader path:state.spinner"></div>
|
244
255
|
</div>
|
245
256
|
`;
|
@@ -69,7 +69,7 @@ class Reload extends CustomElement {
|
|
69
69
|
* @property {Object} templates Template definitions
|
70
70
|
* @property {string} templates.main Main template
|
71
71
|
* @property {string} url url to fetch
|
72
|
-
* @property {string} reload onshow, always
|
72
|
+
* @property {string} reload onshow, always (onshow is default and means that the content is loaded when the element is visible, always means that the content is always loaded)
|
73
73
|
* @property {string} filter css selector
|
74
74
|
* @property {Object} fetch fetch options for the request
|
75
75
|
* @property {string} fetch.redirect error, follow, manual
|
@@ -87,7 +87,7 @@ class Reload extends CustomElement {
|
|
87
87
|
templates: {
|
88
88
|
main: getTemplate.call(this),
|
89
89
|
},
|
90
|
-
shadowMode:
|
90
|
+
shadowMode: false,
|
91
91
|
url: null,
|
92
92
|
reload: "onshow",
|
93
93
|
filter: null,
|
@@ -171,7 +171,13 @@ class Reload extends CustomElement {
|
|
171
171
|
this.setAttribute(ATTRIBUTE_FORM_URL, `${url}`);
|
172
172
|
}
|
173
173
|
|
174
|
-
|
174
|
+
try {
|
175
|
+
return loadContent.call(this);
|
176
|
+
} catch (e) {
|
177
|
+
addErrorAttribute(this, e);
|
178
|
+
return Promise.reject(e)
|
179
|
+
}
|
180
|
+
|
175
181
|
}
|
176
182
|
}
|
177
183
|
|
@@ -290,7 +296,7 @@ function loadContent() {
|
|
290
296
|
}
|
291
297
|
})
|
292
298
|
.catch((e) => {
|
293
|
-
|
299
|
+
addErrorAttribute(this, e);
|
294
300
|
});
|
295
301
|
}
|
296
302
|
|
@@ -16,124 +16,117 @@ import { isString } from "../../../types/is.mjs";
|
|
16
16
|
import { fireCustomEvent } from "../../../dom/events.mjs";
|
17
17
|
import { validateInstance, validateString } from "../../../types/validate.mjs";
|
18
18
|
|
19
|
+
/**
|
20
|
+
* Traverse the element's ancestors to find an existing Shadow DOM.
|
21
|
+
*
|
22
|
+
* @param {HTMLElement} element - The starting element.
|
23
|
+
* @returns {ShadowRoot|null} - The found Shadow DOM or null if none exists.
|
24
|
+
*/
|
19
25
|
function findShadowRoot(element) {
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
while (element) {
|
27
|
+
if (element?.shadowRoot) {
|
28
|
+
return element?.shadowRoot;
|
29
|
+
}
|
30
|
+
element = element?.parentNode;
|
31
|
+
}
|
32
|
+
return null;
|
23
33
|
}
|
24
34
|
|
25
35
|
/**
|
26
|
-
*
|
27
|
-
*
|
28
|
-
*
|
29
|
-
*
|
30
|
-
* @param {
|
31
|
-
* @
|
32
|
-
* @
|
33
|
-
* @
|
34
|
-
* @
|
35
|
-
* @throws {
|
36
|
-
* @throws {TypeError}
|
36
|
+
* Loads content from a URL and assigns it to an element.
|
37
|
+
* Optionally, the loaded content can be filtered using a CSS selector.
|
38
|
+
* Additionally, any <script> elements within the content are extracted and executed.
|
39
|
+
*
|
40
|
+
* @param {HTMLElement} element - The target element to insert the content.
|
41
|
+
* @param {string|URL} url - The URL from which to load the content.
|
42
|
+
* @param {Object} options - Options for the fetch call.
|
43
|
+
* @param {string} [filter] - Optional CSS selector to filter the loaded content.
|
44
|
+
* @returns {Promise<Object>} A promise that resolves to an object containing { content: string, type: string | null }.
|
45
|
+
* @throws {Error} When the content cannot be read or the response contains an error.
|
46
|
+
* @throws {TypeError} When the provided parameters do not match the expected types.
|
37
47
|
*/
|
38
48
|
function loadAndAssignContent(element, url, options, filter) {
|
39
49
|
return loadContent(url, options).then((response) => {
|
40
50
|
let content = response.content;
|
41
51
|
|
52
|
+
// Optional filtering: if a valid, non-empty CSS selector is provided,
|
53
|
+
// only the matching elements will be retained.
|
42
54
|
if (isString(filter) && filter !== "") {
|
43
|
-
const
|
44
|
-
const
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
content =
|
55
|
+
const filteredContainer = document.createElement("div");
|
56
|
+
const tempContainer = document.createElement("div");
|
57
|
+
tempContainer.innerHTML = content;
|
58
|
+
const matchingNodes = tempContainer.querySelectorAll(filter);
|
59
|
+
matchingNodes.forEach((node) => {
|
60
|
+
filteredContainer.appendChild(node);
|
61
|
+
});
|
62
|
+
content = filteredContainer.innerHTML;
|
51
63
|
}
|
52
64
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
if (
|
62
|
-
if (
|
63
|
-
if (
|
64
|
-
if (
|
65
|
-
if (
|
66
|
-
if (
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
// Temporary container for processing the content and extracting scripts
|
66
|
+
const tempDiv = document.createElement("div");
|
67
|
+
tempDiv.innerHTML = content;
|
68
|
+
|
69
|
+
// Extract and execute all <script> elements by appending them to the document head
|
70
|
+
const scriptElements = tempDiv.querySelectorAll("script");
|
71
|
+
scriptElements.forEach((oldScript) => {
|
72
|
+
const newScript = document.createElement("script");
|
73
|
+
if (oldScript.src) newScript.src = oldScript.src;
|
74
|
+
if (oldScript.type) newScript.type = oldScript.type;
|
75
|
+
if (oldScript.async) newScript.async = oldScript.async;
|
76
|
+
if (oldScript.defer) newScript.defer = oldScript.defer;
|
77
|
+
if (oldScript.crossOrigin) newScript.crossOrigin = oldScript.crossOrigin;
|
78
|
+
if (oldScript.integrity) newScript.integrity = oldScript.integrity;
|
79
|
+
if (oldScript.referrerPolicy) newScript.referrerPolicy = oldScript.referrerPolicy;
|
80
|
+
newScript.textContent = oldScript.textContent;
|
81
|
+
document.head.appendChild(newScript);
|
82
|
+
if (oldScript.parentNode) {
|
83
|
+
oldScript.parentNode.removeChild(oldScript);
|
84
|
+
}
|
85
|
+
});
|
70
86
|
|
71
|
-
|
87
|
+
// Assign the processed content to the target element
|
88
|
+
validateInstance(element, HTMLElement).innerHTML = tempDiv.innerHTML;
|
72
89
|
|
73
|
-
|
74
|
-
|
75
|
-
|
90
|
+
// If the element is within a Shadow DOM, use the host as the event target
|
91
|
+
const shadowRoot = findShadowRoot(element);
|
92
|
+
const eventTarget = shadowRoot !== null ? shadowRoot.host : element;
|
93
|
+
if (eventTarget instanceof HTMLElement) {
|
94
|
+
fireCustomEvent(eventTarget, "monster-fetched", { url });
|
76
95
|
}
|
77
96
|
|
78
|
-
fireCustomEvent(element, "monster-fetched", {
|
79
|
-
url,
|
80
|
-
});
|
81
|
-
|
82
97
|
return response;
|
83
98
|
});
|
84
99
|
}
|
85
100
|
|
86
101
|
/**
|
87
|
-
*
|
88
|
-
*
|
89
|
-
*
|
90
|
-
* @
|
91
|
-
* @
|
92
|
-
* @
|
93
|
-
* @throws {Error}
|
94
|
-
* @throws {TypeError}
|
102
|
+
* Loads content from a URL using fetch and returns an object with the loaded content
|
103
|
+
* and the Content-Type header.
|
104
|
+
*
|
105
|
+
* @param {string|URL} url - The URL from which to load the content.
|
106
|
+
* @param {Object} options - Options for the fetch call.
|
107
|
+
* @returns {Promise<Object>} A promise that resolves to an object { content: string, type: string | null }.
|
108
|
+
* @throws {Error} When the content cannot be read or the response contains an error.
|
109
|
+
* @throws {TypeError} When the URL cannot be validated as a string.
|
95
110
|
*/
|
96
111
|
function loadContent(url, options) {
|
97
112
|
if (url instanceof URL) {
|
98
113
|
url = url.toString();
|
99
114
|
}
|
100
|
-
|
101
115
|
return fetch(validateString(url), options).then((response) => {
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
// @see https://developer.mozilla.org/en-US/docs/Web/API/Response/type
|
106
|
-
if (
|
107
|
-
["error", "opaque", "opaqueredirect"].indexOf(response?.type) !== -1
|
108
|
-
) {
|
109
|
-
throw new Error(
|
110
|
-
`we won't be able to read the data (${response?.type})`,
|
111
|
-
);
|
116
|
+
if (!response.ok) {
|
117
|
+
if (["error", "opaque", "opaqueredirect"].includes(response.type)) {
|
118
|
+
throw new Error(`we won't be able to read the data (${response.type})`);
|
112
119
|
}
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
case "4":
|
117
|
-
throw new Error(`client error ${response?.statusText}`);
|
118
|
-
break;
|
119
|
-
default:
|
120
|
-
throw new Error(
|
121
|
-
`undefined status (${response?.status} / ${response?.statusText}) or type (${response?.type})`,
|
122
|
-
);
|
120
|
+
const statusClass = String(response.status).charAt(0);
|
121
|
+
if (statusClass === "4") {
|
122
|
+
throw new Error(`client error ${response.statusText}`);
|
123
123
|
}
|
124
|
+
throw new Error(`undefined status (${response.status} / ${response.statusText}) or type (${response.type})`);
|
124
125
|
}
|
125
|
-
|
126
|
-
|
127
|
-
response
|
128
|
-
|
129
|
-
.then((content) => {
|
130
|
-
resolve({
|
131
|
-
content,
|
132
|
-
type: response.headers.get("Content-Type"),
|
133
|
-
});
|
134
|
-
})
|
135
|
-
.catch(reject);
|
136
|
-
});
|
126
|
+
return response.text().then((content) => ({
|
127
|
+
content,
|
128
|
+
type: response.headers.get("Content-Type"),
|
129
|
+
}));
|
137
130
|
});
|
138
131
|
}
|
139
132
|
|
@@ -32,8 +32,20 @@ class DataFetchError extends Error {
|
|
32
32
|
*/
|
33
33
|
constructor(message, response) {
|
34
34
|
super(message);
|
35
|
+
|
36
|
+
let body = null
|
37
|
+
|
38
|
+
if (response instanceof Response) {
|
39
|
+
body = response.text();
|
40
|
+
}
|
41
|
+
|
42
|
+
if(!(body instanceof Promise)) {
|
43
|
+
body = Promise.resolve(body);
|
44
|
+
}
|
45
|
+
|
35
46
|
this[internalSymbol] = {
|
36
47
|
response: response,
|
48
|
+
body : body
|
37
49
|
};
|
38
50
|
}
|
39
51
|
|
@@ -47,10 +59,17 @@ class DataFetchError extends Error {
|
|
47
59
|
);
|
48
60
|
}
|
49
61
|
|
62
|
+
/**
|
63
|
+
* @return {string|Object}
|
64
|
+
*/
|
65
|
+
getBody() {
|
66
|
+
return this[internalSymbol]?.["body"];
|
67
|
+
}
|
68
|
+
|
50
69
|
/**
|
51
70
|
* @return {Response}
|
52
71
|
*/
|
53
72
|
getResponse() {
|
54
|
-
return this[internalSymbol]["response"];
|
73
|
+
return this[internalSymbol]?.["response"];
|
55
74
|
}
|
56
75
|
}
|
@@ -245,7 +245,7 @@ function fetchData(init, key, callback) {
|
|
245
245
|
if (body.length > 100) {
|
246
246
|
body = `${body.substring(0, 97)}...`;
|
247
247
|
}
|
248
|
-
|
248
|
+
|
249
249
|
throw new DataFetchError(
|
250
250
|
getInternalLocalizationMessage(
|
251
251
|
`i18n{the-response-does-not-contain-a-valid-json::actual=${body}}`,
|
@@ -257,6 +257,7 @@ function fetchData(init, key, callback) {
|
|
257
257
|
if (callback && isFunction(callback)) {
|
258
258
|
callback(obj);
|
259
259
|
}
|
260
|
+
|
260
261
|
return response;
|
261
262
|
})
|
262
263
|
.catch((e) => {
|