@schukai/monster 4.137.1 → 4.137.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/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.2"}
|
|
@@ -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
|
+
});
|