@xiboplayer/renderer 0.7.13 → 0.7.14
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/package.json +4 -4
- package/src/layout.js +21 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/renderer",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.14",
|
|
4
4
|
"description": "RendererLite - Fast, efficient XLF layout rendering engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"pdfjs-dist": "^5.6.205",
|
|
15
|
-
"@xiboplayer/utils": "0.7.
|
|
16
|
-
"@xiboplayer/
|
|
17
|
-
"@xiboplayer/
|
|
15
|
+
"@xiboplayer/utils": "0.7.14",
|
|
16
|
+
"@xiboplayer/cache": "0.7.14",
|
|
17
|
+
"@xiboplayer/schedule": "0.7.14"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"jsdom": "^29.0.1",
|
package/src/layout.js
CHANGED
|
@@ -10,6 +10,18 @@ import { createLogger, isDebug, PLAYER_API } from '@xiboplayer/utils';
|
|
|
10
10
|
|
|
11
11
|
const log = createLogger('Layout');
|
|
12
12
|
|
|
13
|
+
// ── Safe interpolation helpers for HTML generation ─────────────────────────
|
|
14
|
+
// Use these instead of raw ${value} in template literals to prevent XSS.
|
|
15
|
+
// Our LayoutTranslator generates complete HTML documents as strings (unlike
|
|
16
|
+
// upstream Xibo players that use DOM APIs). This gives us pre-rendering and
|
|
17
|
+
// cross-context support (Node.js, arexibo, service worker) but requires
|
|
18
|
+
// manual sanitization at every interpolation point.
|
|
19
|
+
const SAFE_CSS_COLOR = /^(#[0-9a-fA-F]{3,8}|rgba?\(\s*[\d.,\s%]+\)|[a-zA-Z]{1,20}|transparent|inherit)$/;
|
|
20
|
+
export const safeCssColor = (v) => SAFE_CSS_COLOR.test(v) ? v : '#000000';
|
|
21
|
+
export const safeJsString = (v) => JSON.stringify(v);
|
|
22
|
+
export const safeHtmlAttr = (v) => String(v).replace(/[&"'<>]/g, c =>
|
|
23
|
+
({ '&': '&', '"': '"', "'": ''', '<': '<', '>': '>' }[c]));
|
|
24
|
+
|
|
13
25
|
export class LayoutTranslator {
|
|
14
26
|
constructor(xmds) {
|
|
15
27
|
this.xmds = xmds;
|
|
@@ -29,7 +41,7 @@ export class LayoutTranslator {
|
|
|
29
41
|
|
|
30
42
|
const width = parseInt(layoutEl.getAttribute('width') || '1920');
|
|
31
43
|
const height = parseInt(layoutEl.getAttribute('height') || '1080');
|
|
32
|
-
const bgcolor = layoutEl.getAttribute('bgcolor') || '#000000';
|
|
44
|
+
const bgcolor = safeCssColor(layoutEl.getAttribute('bgcolor') || '#000000');
|
|
33
45
|
|
|
34
46
|
const regions = [];
|
|
35
47
|
for (const regionEl of doc.querySelectorAll('region')) {
|
|
@@ -416,7 +428,7 @@ ${mediaJS}
|
|
|
416
428
|
if (!iframe) {
|
|
417
429
|
iframe = document.createElement('iframe');
|
|
418
430
|
iframe.id = '${iframeId}';
|
|
419
|
-
iframe.src =
|
|
431
|
+
iframe.src = ${safeJsString(widgetUrl)};
|
|
420
432
|
iframe.style.width = '100%';
|
|
421
433
|
iframe.style.height = '100%';
|
|
422
434
|
iframe.style.border = 'none';
|
|
@@ -479,7 +491,7 @@ ${mediaJS}
|
|
|
479
491
|
const region = document.getElementById('region_${regionId}');
|
|
480
492
|
const img = document.createElement('img');
|
|
481
493
|
img.className = 'media';
|
|
482
|
-
img.src =
|
|
494
|
+
img.src = ${safeJsString(imageSrc)};
|
|
483
495
|
img.style.opacity = '0';
|
|
484
496
|
region.innerHTML = '';
|
|
485
497
|
region.appendChild(img);
|
|
@@ -506,8 +518,8 @@ ${mediaJS}
|
|
|
506
518
|
const region = document.getElementById('region_${regionId}');
|
|
507
519
|
const video = document.createElement('video');
|
|
508
520
|
video.className = 'media';
|
|
509
|
-
video.src =
|
|
510
|
-
video.dataset.filename =
|
|
521
|
+
video.src = ${safeJsString(videoSrc)};
|
|
522
|
+
video.dataset.filename = ${safeJsString(videoFilename)};
|
|
511
523
|
video.autoplay = true;
|
|
512
524
|
video.muted = ${media.options.mute === '1' ? 'true' : 'false'};
|
|
513
525
|
video.loop = false;
|
|
@@ -518,7 +530,7 @@ ${mediaJS}
|
|
|
518
530
|
|
|
519
531
|
// Retry loading if cache completes while video is playing
|
|
520
532
|
const retryOnCache = (event) => {
|
|
521
|
-
if (event.detail.filename ===
|
|
533
|
+
if (event.detail.filename === ${safeJsString(videoFilename)} && video.error) {
|
|
522
534
|
console.log('[Video] Cache complete, reloading:', '${videoFilename}');
|
|
523
535
|
video.load();
|
|
524
536
|
video.play();
|
|
@@ -595,7 +607,7 @@ ${mediaJS}
|
|
|
595
607
|
const audio = document.createElement('audio');
|
|
596
608
|
audio.id = '${audioId}';
|
|
597
609
|
audio.className = 'media';
|
|
598
|
-
audio.src =
|
|
610
|
+
audio.src = ${safeJsString(audioSrc)};
|
|
599
611
|
audio.autoplay = true;
|
|
600
612
|
audio.loop = ${audioLoop};
|
|
601
613
|
audio.volume = ${audioVolume};
|
|
@@ -734,7 +746,7 @@ ${mediaJS}
|
|
|
734
746
|
|
|
735
747
|
// Render PDF with multi-page support
|
|
736
748
|
try {
|
|
737
|
-
const loadingTask = pdfjsLib.getDocument(
|
|
749
|
+
const loadingTask = pdfjsLib.getDocument(${safeJsString(pdfSrc)});
|
|
738
750
|
const pdf = await loadingTask.promise;
|
|
739
751
|
const totalPages = pdf.numPages;
|
|
740
752
|
|
|
@@ -893,7 +905,7 @@ ${mediaJS}
|
|
|
893
905
|
startFn = `() => {
|
|
894
906
|
const region = document.getElementById('region_${regionId}');
|
|
895
907
|
const iframe = document.createElement('iframe');
|
|
896
|
-
iframe.src =
|
|
908
|
+
iframe.src = ${safeJsString(url)};
|
|
897
909
|
iframe.style.opacity = '0';
|
|
898
910
|
region.innerHTML = '';
|
|
899
911
|
region.appendChild(iframe);
|