@schukai/monster 4.137.1 → 4.137.3
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/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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":"4.137.
|
|
1
|
+
{"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"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":"4.137.3"}
|
|
@@ -580,6 +580,7 @@ function initPopperSwitch() {
|
|
|
580
580
|
}
|
|
581
581
|
|
|
582
582
|
this[switchElementSymbol] = switchButton;
|
|
583
|
+
updatePopperSwitchVisibility.call(this);
|
|
583
584
|
}
|
|
584
585
|
|
|
585
586
|
/**
|
|
@@ -1180,25 +1181,36 @@ function adjustButtonVisibility() {
|
|
|
1180
1181
|
const self = this;
|
|
1181
1182
|
|
|
1182
1183
|
return new Promise((resolve) => {
|
|
1183
|
-
|
|
1184
|
+
let resolved = false;
|
|
1185
|
+
let observer;
|
|
1186
|
+
const runIfRendered = () => {
|
|
1187
|
+
if (resolved === true) return;
|
|
1188
|
+
|
|
1184
1189
|
const defCount = self.getOption("buttons.standard").length;
|
|
1185
1190
|
const domCount = self[navElementSymbol].querySelectorAll(
|
|
1186
|
-
'button[data-monster-role="button"]',
|
|
1191
|
+
'button[data-monster-role="button"][data-monster-tab-reference]',
|
|
1187
1192
|
).length;
|
|
1188
1193
|
|
|
1189
|
-
// in drawing
|
|
1190
1194
|
if (defCount !== domCount) return;
|
|
1191
1195
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1196
|
+
resolved = true;
|
|
1197
|
+
observer?.disconnect();
|
|
1194
1198
|
checkAndRearrangeButtons.call(self);
|
|
1195
|
-
|
|
1196
1199
|
resolve();
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
observer = new MutationObserver(function () {
|
|
1203
|
+
runIfRendered();
|
|
1197
1204
|
});
|
|
1198
1205
|
|
|
1199
1206
|
observer.observe(self[navElementSymbol], {
|
|
1200
1207
|
attributes: true,
|
|
1208
|
+
childList: true,
|
|
1209
|
+
subtree: true,
|
|
1201
1210
|
});
|
|
1211
|
+
|
|
1212
|
+
runIfRendered();
|
|
1213
|
+
getWindow().requestAnimationFrame(runIfRendered);
|
|
1202
1214
|
});
|
|
1203
1215
|
}
|
|
1204
1216
|
|
|
@@ -1273,16 +1285,25 @@ function rearrangeButtons() {
|
|
|
1273
1285
|
|
|
1274
1286
|
setButtonCollections.call(this, standardButtons, popperButtons);
|
|
1275
1287
|
|
|
1276
|
-
|
|
1277
|
-
if (popperButtons.length > 0) {
|
|
1278
|
-
this[switchElementSymbol].classList.remove("hidden");
|
|
1279
|
-
} else {
|
|
1280
|
-
this[switchElementSymbol].classList.add("hidden");
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1288
|
+
updatePopperSwitchVisibility.call(this);
|
|
1283
1289
|
});
|
|
1284
1290
|
}
|
|
1285
1291
|
|
|
1292
|
+
/**
|
|
1293
|
+
* @private
|
|
1294
|
+
*/
|
|
1295
|
+
function updatePopperSwitchVisibility() {
|
|
1296
|
+
if (!this[switchElementSymbol]) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
if (this.getOption("buttons.popper").length > 0) {
|
|
1301
|
+
this[switchElementSymbol].classList.remove("hidden");
|
|
1302
|
+
} else {
|
|
1303
|
+
this[switchElementSymbol].classList.add("hidden");
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1286
1307
|
/**
|
|
1287
1308
|
* @private
|
|
1288
1309
|
* @param {string} ref
|
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
import { internalStateSymbol } from "../../constants.mjs";
|
|
16
16
|
import { extend } from "../../data/extend.mjs";
|
|
17
17
|
import { getGlobalFunction } from "../../types/global.mjs";
|
|
18
|
-
import { addAttributeToken } from "../attributes.mjs";
|
|
19
18
|
import {
|
|
20
19
|
ATTRIBUTE_CLASS,
|
|
21
|
-
ATTRIBUTE_ERRORMESSAGE,
|
|
22
20
|
ATTRIBUTE_ID,
|
|
23
21
|
ATTRIBUTE_SRC,
|
|
24
22
|
ATTRIBUTE_TITLE,
|
|
@@ -35,6 +33,9 @@ import { instanceSymbol } from "../../constants.mjs";
|
|
|
35
33
|
|
|
36
34
|
export { Data };
|
|
37
35
|
|
|
36
|
+
const KEY_ALLOWED_ORIGINS = "allowedOrigins";
|
|
37
|
+
const JSON_SCRIPT_TYPE_PATTERN = /^(application\/json|text\/json|application\/[a-z0-9.+-]+\+json)$/i;
|
|
38
|
+
|
|
38
39
|
/**
|
|
39
40
|
* This class is used by the resource manager to embed data.
|
|
40
41
|
*
|
|
@@ -54,6 +55,7 @@ class Data extends Resource {
|
|
|
54
55
|
mode: "cors",
|
|
55
56
|
credentials: "same-origin",
|
|
56
57
|
type: "application/json",
|
|
58
|
+
[KEY_ALLOWED_ORIGINS]: ["self"],
|
|
57
59
|
});
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -73,7 +75,6 @@ class Data extends Resource {
|
|
|
73
75
|
* @return {Monster.DOM.Resource}
|
|
74
76
|
*/
|
|
75
77
|
connect() {
|
|
76
|
-
const self = this;
|
|
77
78
|
if (!(this[referenceSymbol] instanceof HTMLElement)) {
|
|
78
79
|
this.create();
|
|
79
80
|
}
|
|
@@ -105,6 +106,12 @@ class Data extends Resource {
|
|
|
105
106
|
*/
|
|
106
107
|
function createElement() {
|
|
107
108
|
const document = this.getOption(KEY_DOCUMENT);
|
|
109
|
+
const type = this.getOption(ATTRIBUTE_TYPE);
|
|
110
|
+
|
|
111
|
+
if (!isInertJSONType(type)) {
|
|
112
|
+
throw new Error("unsupported data resource type");
|
|
113
|
+
}
|
|
114
|
+
|
|
108
115
|
this[referenceSymbol] = document.createElement(TAG_SCRIPT);
|
|
109
116
|
|
|
110
117
|
for (const key of [
|
|
@@ -127,6 +134,8 @@ function createElement() {
|
|
|
127
134
|
* throws {Error} target not found
|
|
128
135
|
*/
|
|
129
136
|
function appendToDocument() {
|
|
137
|
+
const document = this.getOption(KEY_DOCUMENT);
|
|
138
|
+
const url = getValidatedURL.call(this, document);
|
|
130
139
|
const targetNode = document.querySelector(this.getOption(KEY_QUERY, "head"));
|
|
131
140
|
if (!(targetNode instanceof HTMLElement)) {
|
|
132
141
|
throw new Error("target not found");
|
|
@@ -134,7 +143,7 @@ function appendToDocument() {
|
|
|
134
143
|
|
|
135
144
|
targetNode.appendChild(this[referenceSymbol]);
|
|
136
145
|
|
|
137
|
-
getGlobalFunction("fetch")(
|
|
146
|
+
getGlobalFunction("fetch")(url.toString(), {
|
|
138
147
|
method: "GET", // *GET, POST, PUT, DELETE, etc.
|
|
139
148
|
mode: this.getOption("mode", "cors"), // no-cors, *cors, same-origin
|
|
140
149
|
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
|
|
@@ -159,9 +168,48 @@ function appendToDocument() {
|
|
|
159
168
|
loaded: true,
|
|
160
169
|
error: e.toString(),
|
|
161
170
|
});
|
|
162
|
-
|
|
163
|
-
targetNode.setAttribute(ATTRIBUTE_ERRORMESSAGE, e.toString());
|
|
164
171
|
});
|
|
165
172
|
|
|
166
173
|
return this;
|
|
167
174
|
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @private
|
|
178
|
+
* @param {string} type
|
|
179
|
+
* @return {boolean}
|
|
180
|
+
*/
|
|
181
|
+
function isInertJSONType(type) {
|
|
182
|
+
if (type === undefined) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return JSON_SCRIPT_TYPE_PATTERN.test(String(type).split(";")[0].trim());
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @private
|
|
191
|
+
* @param {Document} document
|
|
192
|
+
* @return {URL}
|
|
193
|
+
*/
|
|
194
|
+
function getValidatedURL(document) {
|
|
195
|
+
const url = new URL(this.getOption(ATTRIBUTE_SRC), document.baseURI);
|
|
196
|
+
|
|
197
|
+
if (!["http:", "https:"].includes(url.protocol)) {
|
|
198
|
+
throw new Error("unsupported data resource protocol");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const allowedOriginsOption = this.getOption(KEY_ALLOWED_ORIGINS, ["self"]);
|
|
202
|
+
const allowedOrigins = Array.isArray(allowedOriginsOption)
|
|
203
|
+
? allowedOriginsOption
|
|
204
|
+
: [allowedOriginsOption];
|
|
205
|
+
const documentOrigin = new URL(document.baseURI).origin;
|
|
206
|
+
const allowed = allowedOrigins.map((origin) =>
|
|
207
|
+
origin === "self" ? documentOrigin : new URL(origin, document.baseURI).origin,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (!allowed.includes(url.origin)) {
|
|
211
|
+
throw new Error("data resource origin not allowed");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return url;
|
|
215
|
+
}
|
|
@@ -63,7 +63,7 @@ describe('Data', function () {
|
|
|
63
63
|
it('setEventTypes()', function (done) {
|
|
64
64
|
|
|
65
65
|
const data = new Data({
|
|
66
|
-
src:
|
|
66
|
+
src: '/data.json'
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
data.connect().available().then(() => {
|
|
@@ -71,12 +71,77 @@ describe('Data', function () {
|
|
|
71
71
|
}).catch(e => done(e));
|
|
72
72
|
|
|
73
73
|
})
|
|
74
|
+
|
|
75
|
+
it('rejects executable script types', function () {
|
|
76
|
+
|
|
77
|
+
const data = new Data({
|
|
78
|
+
src: new DataUrl('console.log(1);', 'text/javascript').toString(),
|
|
79
|
+
type: 'text/javascript'
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(() => data.connect()).to.throw('unsupported data resource type');
|
|
83
|
+
expect(document.querySelector('script')).not.to.exist;
|
|
84
|
+
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('rejects remote origins by default', function () {
|
|
88
|
+
|
|
89
|
+
const data = new Data({
|
|
90
|
+
src: 'https://cdn.example/data.json'
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(() => data.connect()).to.throw('data resource origin not allowed');
|
|
94
|
+
expect(document.querySelector('script')).not.to.exist;
|
|
95
|
+
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('allows configured remote origins', function (done) {
|
|
99
|
+
|
|
100
|
+
const data = new Data({
|
|
101
|
+
src: 'https://cdn.example/data.json',
|
|
102
|
+
allowedOrigins: ['https://cdn.example']
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
data.connect().available().then(() => {
|
|
106
|
+
expect(data.isConnected()).to.be.true;
|
|
107
|
+
|
|
108
|
+
done();
|
|
109
|
+
}).catch(e => done(e));
|
|
110
|
+
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('does not expose fetch errors in DOM attributes', function (done) {
|
|
114
|
+
|
|
115
|
+
globalThis['fetch'] = function () {
|
|
116
|
+
return Promise.reject(new Error('token=secret'));
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const data = new Data({
|
|
120
|
+
src: '/data.json',
|
|
121
|
+
id: 'data-error'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
data.connect();
|
|
125
|
+
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
try {
|
|
128
|
+
const script = document.getElementById('data-error');
|
|
129
|
+
|
|
130
|
+
expect(script.hasAttribute('data-monster-error')).to.be.false;
|
|
131
|
+
|
|
132
|
+
done();
|
|
133
|
+
} catch (e) {
|
|
134
|
+
done(e);
|
|
135
|
+
}
|
|
136
|
+
}, 0);
|
|
137
|
+
|
|
138
|
+
})
|
|
74
139
|
});
|
|
75
140
|
|
|
76
141
|
describe('External Data', () => {
|
|
77
142
|
|
|
78
143
|
let id = new ID('data').toString();
|
|
79
|
-
let server, data, url = '
|
|
144
|
+
let server, data, url = '/layzr.json';
|
|
80
145
|
|
|
81
146
|
beforeEach(() => {
|
|
82
147
|
|
|
@@ -126,4 +191,4 @@ describe('Data', function () {
|
|
|
126
191
|
});
|
|
127
192
|
|
|
128
193
|
|
|
129
|
-
});
|
|
194
|
+
});
|
|
@@ -104,7 +104,7 @@ describe('ResourceManager', function () {
|
|
|
104
104
|
|
|
105
105
|
describe('check availability example.json', function () {
|
|
106
106
|
it('add data and check content', function (done) {
|
|
107
|
-
manager.addData('
|
|
107
|
+
manager.addData('/example.json').connect().available().then(r => {
|
|
108
108
|
expect(document.querySelector('html').outerHTML).contains('>{"a":"test"}</script></head>');
|
|
109
109
|
done();
|
|
110
110
|
}).catch(e => done(e));
|
|
@@ -115,4 +115,4 @@ describe('ResourceManager', function () {
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
});
|
|
118
|
+
});
|