@kupola/kupola 1.5.1 → 1.5.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/js/depends.js CHANGED
@@ -299,11 +299,16 @@ class FetchedSource extends DependsSource {
299
299
  url += (url.includes('?') ? '&' : '?') + queryParts.join('&');
300
300
  }
301
301
 
302
+ const globalHeaders = httpConfig?.headers || {};
302
303
  const fetchOptions = {
303
304
  method: this.method.toUpperCase(),
304
- headers: { 'Content-Type': 'application/json', ...this.headers }
305
+ headers: { 'Content-Type': 'application/json', ...globalHeaders, ...this.headers }
305
306
  };
306
307
 
308
+ if (httpConfig?.withCredentials) {
309
+ fetchOptions.credentials = 'include';
310
+ }
311
+
307
312
  if (['POST', 'PUT', 'PATCH'].includes(fetchOptions.method)) {
308
313
  fetchOptions.body = JSON.stringify(params);
309
314
  }
@@ -13,7 +13,39 @@ const config = {
13
13
  },
14
14
  http: {
15
15
  baseURL: '',
16
- timeout: 10000
16
+ timeout: 10000,
17
+ headers: {},
18
+ withCredentials: false
19
+ },
20
+ ui: {
21
+ defaultSize: 'md',
22
+ defaultVariant: 'default'
23
+ },
24
+ performance: {
25
+ lazyLoad: true,
26
+ debounceDelay: 300,
27
+ throttleDelay: 100,
28
+ animationEnabled: true
29
+ },
30
+ security: {
31
+ xssProtection: true,
32
+ sanitizeHtml: {
33
+ enabled: true,
34
+ allowedTags: ['b', 'i', 'u', 'em', 'strong', 'a', 'br', 'p', 'span', 'div', 'img'],
35
+ allowedAttributes: {
36
+ 'a': ['href', 'target', 'rel'],
37
+ 'img': ['src', 'alt', 'width', 'height'],
38
+ 'span': ['class', 'style'],
39
+ 'div': ['class', 'style']
40
+ }
41
+ }
42
+ },
43
+ dev: {
44
+ debug: false,
45
+ logLevel: 'warn'
46
+ },
47
+ extend: {
48
+ themeVariables: {}
17
49
  },
18
50
  components: {
19
51
  autoInit: true,
@@ -53,6 +85,30 @@ export function getDefaultBrand() {
53
85
  return config.theme.brand;
54
86
  }
55
87
 
88
+ export function getHttpConfig() {
89
+ return config.http;
90
+ }
91
+
92
+ export function getUiConfig() {
93
+ return config.ui;
94
+ }
95
+
96
+ export function getSecurityConfig() {
97
+ return config.security;
98
+ }
99
+
100
+ export function getDevConfig() {
101
+ return config.dev;
102
+ }
103
+
104
+ export function getPerformanceConfig() {
105
+ return config.performance;
106
+ }
107
+
108
+ export function getExtendConfig() {
109
+ return config.extend;
110
+ }
111
+
56
112
  function mergeDeep(target, source) {
57
113
  for (const key in source) {
58
114
  if (source[key] instanceof Object && key in target && target[key] instanceof Object) {
package/js/kupola-core.js CHANGED
@@ -9,7 +9,7 @@ import { KupolaLifecycle } from './kupola-lifecycle.js';
9
9
  import { kupolaInitializer } from './initializer.js';
10
10
  import { kupolaData } from './data-bind.js';
11
11
  import { initTheme } from './theme.js';
12
- import { getConfig } from './kupola-config.js';
12
+ import { getConfig, getSecurityConfig } from './kupola-config.js';
13
13
 
14
14
  let kupolaRegistry = null;
15
15
 
@@ -17,9 +17,41 @@ if (typeof window !== 'undefined') {
17
17
  kupolaRegistry = new KupolaComponentRegistry();
18
18
  }
19
19
 
20
+ function _applySecurityHeaders() {
21
+ const securityConfig = getSecurityConfig();
22
+
23
+ if (securityConfig.xssProtection && typeof document !== 'undefined') {
24
+ let metaTag = document.querySelector('meta[http-equiv="X-XSS-Protection"]');
25
+ if (!metaTag) {
26
+ metaTag = document.createElement('meta');
27
+ metaTag.setAttribute('http-equiv', 'X-XSS-Protection');
28
+ metaTag.setAttribute('content', '1; mode=block');
29
+ document.head.insertBefore(metaTag, document.head.firstChild);
30
+ }
31
+
32
+ metaTag = document.querySelector('meta[http-equiv="X-Content-Type-Options"]');
33
+ if (!metaTag) {
34
+ metaTag = document.createElement('meta');
35
+ metaTag.setAttribute('http-equiv', 'X-Content-Type-Options');
36
+ metaTag.setAttribute('content', 'nosniff');
37
+ document.head.insertBefore(metaTag, document.head.firstChild);
38
+ }
39
+
40
+ metaTag = document.querySelector('meta[http-equiv="X-Frame-Options"]');
41
+ if (!metaTag) {
42
+ metaTag = document.createElement('meta');
43
+ metaTag.setAttribute('http-equiv', 'X-Frame-Options');
44
+ metaTag.setAttribute('content', 'SAMEORIGIN');
45
+ document.head.insertBefore(metaTag, document.head.firstChild);
46
+ }
47
+ }
48
+ }
49
+
20
50
  /** Bootstrap the Kupola component system (data binding, theme, component discovery). */
21
51
  async function kupolaBootstrap() {
22
52
  if (typeof window !== 'undefined') {
53
+ _applySecurityHeaders();
54
+
23
55
  const config = getConfig();
24
56
  // Load persisted data and bind data-bind elements
25
57
  kupolaData.loadPersisted();
package/js/modal.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { kupolaInitializer } from './initializer.js';
2
+ import { getUiConfig } from './kupola-config.js';
2
3
 
3
4
  class Modal {
4
5
  constructor(element, options = {}) {
@@ -182,9 +183,12 @@ function createModal(options = {}) {
182
183
  onCancel,
183
184
  onOpen,
184
185
  onClose,
185
- footer = null
186
+ footer = null,
187
+ size = getUiConfig().defaultSize
186
188
  } = options;
187
189
 
190
+ const sizeClass = size === 'sm' ? 'ds-btn--sm' : size === 'lg' ? 'ds-btn--lg' : '';
191
+
188
192
  const container = document.createElement('div');
189
193
  container.className = 'ds-modal-container';
190
194
 
@@ -194,8 +198,8 @@ function createModal(options = {}) {
194
198
  footerHTML = `<div class="ds-modal__footer">${footer}</div>`;
195
199
  } else if (showConfirm || showCancel) {
196
200
  footerHTML = `<div class="ds-modal__footer">
197
- ${showCancel ? `<button class="ds-btn ${cancelClass}" data-modal-cancel>${cancelText}</button>` : ''}
198
- ${showConfirm ? `<button class="ds-btn ${confirmClass}" data-modal-confirm>${confirmText}</button>` : ''}
201
+ ${showCancel ? `<button class="ds-btn ${sizeClass} ${cancelClass}" data-modal-cancel>${cancelText}</button>` : ''}
202
+ ${showConfirm ? `<button class="ds-btn ${sizeClass} ${confirmClass}" data-modal-confirm>${confirmText}</button>` : ''}
199
203
  </div>`;
200
204
  }
201
205
  }
package/js/security.js ADDED
@@ -0,0 +1,61 @@
1
+ import { getSecurityConfig } from './kupola-config.js';
2
+
3
+ export function sanitizeHtml(html, options = {}) {
4
+ const securityConfig = getSecurityConfig();
5
+ const sanitizeConfig = securityConfig?.sanitizeHtml || {};
6
+
7
+ if (!sanitizeConfig.enabled && !options.force) {
8
+ return html;
9
+ }
10
+
11
+ const allowedTags = options.allowedTags || sanitizeConfig.allowedTags || [];
12
+ const allowedAttributes = options.allowedAttributes || sanitizeConfig.allowedAttributes || {};
13
+
14
+ if (typeof html !== 'string') {
15
+ return html;
16
+ }
17
+
18
+ const domParser = new DOMParser();
19
+ const doc = domParser.parseFromString(html, 'text/html');
20
+ const elements = doc.body.querySelectorAll('*');
21
+
22
+ elements.forEach(element => {
23
+ const tagName = element.tagName.toLowerCase();
24
+
25
+ if (!allowedTags.includes(tagName)) {
26
+ element.remove();
27
+ return;
28
+ }
29
+
30
+ Array.from(element.attributes).forEach(attr => {
31
+ const attrName = attr.name.toLowerCase();
32
+ const tagAllowedAttrs = allowedAttributes[tagName] || [];
33
+
34
+ if (!tagAllowedAttrs.includes(attrName)) {
35
+ element.removeAttribute(attr.name);
36
+ }
37
+ });
38
+ });
39
+
40
+ return doc.body.innerHTML;
41
+ }
42
+
43
+ export function escapeHtml(text) {
44
+ if (typeof text !== 'string') {
45
+ return text;
46
+ }
47
+
48
+ const div = document.createElement('div');
49
+ div.textContent = text;
50
+ return div.innerHTML;
51
+ }
52
+
53
+ export function stripHtml(html) {
54
+ if (typeof html !== 'string') {
55
+ return html;
56
+ }
57
+
58
+ const domParser = new DOMParser();
59
+ const doc = domParser.parseFromString(html, 'text/html');
60
+ return doc.body.textContent || '';
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kupola/kupola",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "A lightweight UI toolkit for any web project. No heavy frontend frameworks required.",
5
5
  "main": "dist/kupola.cjs.js",
6
6
  "module": "dist/kupola.esm.js",