@ecency/render-helper 2.5.11 → 2.5.13

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.
@@ -176,6 +176,96 @@ var ALLOWED_ATTRIBUTES = {
176
176
  "del": [],
177
177
  "ins": []
178
178
  };
179
+
180
+ // src/consts/embed-hosts.const.ts
181
+ var ALLOWED_EMBED_HOSTS = /* @__PURE__ */ new Set([
182
+ // YouTube
183
+ "www.youtube.com",
184
+ "youtube.com",
185
+ "www.youtube-nocookie.com",
186
+ "youtube-nocookie.com",
187
+ // Vimeo
188
+ "player.vimeo.com",
189
+ // Twitch
190
+ "player.twitch.tv",
191
+ // DTube
192
+ "emb.d.tube",
193
+ // 3Speak (video + audio)
194
+ "play.3speak.tv",
195
+ "3speak.tv",
196
+ "audio.3speak.tv",
197
+ // Loom
198
+ "www.loom.com",
199
+ // Spotify
200
+ "open.spotify.com",
201
+ // SoundCloud
202
+ "w.soundcloud.com",
203
+ // BitChute
204
+ "www.bitchute.com",
205
+ "bitchute.com",
206
+ // Rumble
207
+ "www.rumble.com",
208
+ "rumble.com",
209
+ // Brighteon
210
+ "www.brighteon.com",
211
+ "brighteon.com",
212
+ // VIMM
213
+ "www.vimm.tv",
214
+ // BrandNewTube
215
+ "brandnewtube.com",
216
+ // LBRY / Odysee
217
+ "lbry.tv",
218
+ "odysee.com",
219
+ // Skatehive / Skatehype
220
+ "ipfs.skatehive.app",
221
+ "www.skatehype.com",
222
+ "skatehype.com",
223
+ // archive.org
224
+ "archive.org",
225
+ // Truvvl
226
+ "embed.truvvl.com",
227
+ // Aureal
228
+ "aureal-embed.web.app",
229
+ "www.aureal-embed.web.app"
230
+ // Dapplr (player.*.dapplr.in / *.dapplr.in) — host suffix, handled below
231
+ ]);
232
+ var ALLOWED_EMBED_HOST_SUFFIXES = [".dapplr.in"];
233
+ var EMBED_HOST_PATH_PATTERNS = {
234
+ "www.youtube.com": /^\/embed\//,
235
+ "youtube.com": /^\/embed\//,
236
+ "www.youtube-nocookie.com": /^\/embed\//,
237
+ "youtube-nocookie.com": /^\/embed\//,
238
+ "player.vimeo.com": /^\/video\//,
239
+ "player.twitch.tv": /^\/$/,
240
+ // channel/video carried in the query string
241
+ "emb.d.tube": /^\/$/,
242
+ // dtube carries the ref in the #! fragment
243
+ "play.3speak.tv": /^\/(watch|embed)/,
244
+ "open.spotify.com": /^\/embed\//,
245
+ "www.loom.com": /^\/embed\//,
246
+ "www.bitchute.com": /^\/embed\//,
247
+ "bitchute.com": /^\/embed\//,
248
+ "www.rumble.com": /^\/embed\//,
249
+ "rumble.com": /^\/embed\//,
250
+ "www.brighteon.com": /^\/embed\//,
251
+ "brighteon.com": /^\/embed\//
252
+ };
253
+ function isAllowedEmbedSrc(value) {
254
+ if (!value) return false;
255
+ let url;
256
+ try {
257
+ url = new URL(value.trim());
258
+ } catch {
259
+ return false;
260
+ }
261
+ if (url.protocol !== "https:") return false;
262
+ const host = url.hostname.toLowerCase();
263
+ const hostAllowed = ALLOWED_EMBED_HOSTS.has(host) || ALLOWED_EMBED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix));
264
+ if (!hostAllowed) return false;
265
+ const pathPattern = EMBED_HOST_PATH_PATTERNS[host];
266
+ if (pathPattern && !pathPattern.test(url.pathname)) return false;
267
+ return true;
268
+ }
179
269
  function createParser() {
180
270
  return new DOMParser$1({
181
271
  onError(level, msg) {
@@ -571,6 +661,14 @@ function buildPictureSources(rawUrl) {
571
661
  }
572
662
 
573
663
  // src/methods/sanitize-html.method.ts
664
+ var EMBED_SRC_DATA_ATTRS = /* @__PURE__ */ new Set(["data-embed-src", "data-video-href"]);
665
+ var isSafeNavValue = (value) => {
666
+ const trimmed = value.trim().replace(/[\t\n\r\f\v\0]/g, "").toLowerCase();
667
+ if (!trimmed) return false;
668
+ const isSafeScheme = /^(https?|mailto|hive|tel|web\+[a-z0-9.+-]+):/i.test(trimmed);
669
+ const isRelative = /^(\/\/|\/[^/]?|#|\?|[a-z0-9._\-]+(\/|$))/i.test(trimmed);
670
+ return isSafeScheme || isRelative;
671
+ };
574
672
  var decodeEntities = (input) => input.replace(/&#(\d+);?/g, (_, dec) => String.fromCodePoint(Number(dec))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)));
575
673
  var isProxyPSrcset = (srcset) => {
576
674
  const base = trimTrailingSlash(getProxyBase());
@@ -598,6 +696,8 @@ function sanitizeHtml(html) {
598
696
  if (tag === "video" && ["src", "poster"].includes(name) && !/^https?:\/\//.test(decodedLower)) return "";
599
697
  if (tag === "img" && ["dynsrc", "lowsrc"].includes(name)) return "";
600
698
  if (tag === "span" && name === "class" && decoded.toLowerCase().trim() === "wr") return "";
699
+ if (EMBED_SRC_DATA_ATTRS.has(name) && !isAllowedEmbedSrc(decoded)) return "";
700
+ if (name === "data-href" && !isSafeNavValue(decoded)) return "";
601
701
  if (name === "id") {
602
702
  if (!ID_WHITELIST.test(decoded)) return "";
603
703
  }
@@ -1113,14 +1213,15 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
1113
1213
  el.setAttribute("data-start-time", startTime);
1114
1214
  }
1115
1215
  if (renderOptions?.embedVideosDirectly) {
1216
+ const directSrc = `https://www.youtube.com/embed/${vid}`;
1116
1217
  const wrapper = el.ownerDocument.createElement("span");
1117
1218
  wrapper.setAttribute("class", "er-youtube-frame");
1118
1219
  wrapper.setAttribute("style", "display:block");
1119
1220
  const iframe2 = el.ownerDocument.createElement("iframe");
1120
1221
  iframe2.setAttribute("class", "youtube-player");
1121
- iframe2.setAttribute("src", embedSrc);
1222
+ iframe2.setAttribute("src", directSrc);
1122
1223
  iframe2.setAttribute("title", "YouTube video");
1123
- iframe2.setAttribute("allow", "accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; web-share");
1224
+ iframe2.setAttribute("allow", "accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share");
1124
1225
  iframe2.setAttribute("allowfullscreen", "");
1125
1226
  wrapper.appendChild(iframe2);
1126
1227
  el.appendChild(wrapper);
@@ -1257,12 +1358,13 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
1257
1358
  el.textContent = "";
1258
1359
  }
1259
1360
  if (renderOptions?.embedVideosDirectly) {
1361
+ const directSrc = `${videoHref}&autoplay=false`;
1260
1362
  const wrapper = el.ownerDocument.createElement("span");
1261
1363
  wrapper.setAttribute("class", "er-speak-frame");
1262
1364
  wrapper.setAttribute("style", "display:block");
1263
1365
  const iframe2 = el.ownerDocument.createElement("iframe");
1264
1366
  iframe2.setAttribute("class", "speak-iframe");
1265
- iframe2.setAttribute("src", videoHref);
1367
+ iframe2.setAttribute("src", directSrc);
1266
1368
  iframe2.setAttribute("title", "3Speak video");
1267
1369
  iframe2.setAttribute("allow", "accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share");
1268
1370
  iframe2.setAttribute("allowfullscreen", "");
@@ -1375,7 +1477,7 @@ function a(el, forApp, parentDomain = "ecency.com", seoContext, renderOptions) {
1375
1477
  }
1376
1478
 
1377
1479
  // src/methods/iframe.method.ts
1378
- function iframe(el, parentDomain = "ecency.com", forApp = false) {
1480
+ function iframe(el, parentDomain = "ecency.com", forApp = false, renderOptions) {
1379
1481
  if (!el || !el.parentNode) {
1380
1482
  return;
1381
1483
  }
@@ -1418,7 +1520,12 @@ function iframe(el, parentDomain = "ecency.com", forApp = false) {
1418
1520
  normalizedSrc = `${normalizedSrc}&mode=iframe`;
1419
1521
  }
1420
1522
  const hasAutoplay = /[?&]autoplay=/.test(normalizedSrc);
1421
- let s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1523
+ let s;
1524
+ if (renderOptions?.embedVideosDirectly) {
1525
+ s = hasAutoplay ? normalizedSrc.replace(/([?&]autoplay=)[^&]*/i, "$1false") : `${normalizedSrc}&autoplay=false`;
1526
+ } else {
1527
+ s = hasAutoplay ? normalizedSrc : `${normalizedSrc}&autoplay=true`;
1528
+ }
1422
1529
  if (forApp && !/[?&]layout=/.test(s)) {
1423
1530
  s = `${s}&layout=mobile`;
1424
1531
  }
@@ -1732,7 +1839,7 @@ function traverse(node, forApp, depth = 0, state = { firstImageFound: false }, p
1732
1839
  a(child, forApp, parentDomain, seoContext, renderOptions);
1733
1840
  }
1734
1841
  if (child.nodeName.toLowerCase() === "iframe") {
1735
- iframe(child, parentDomain, forApp);
1842
+ iframe(child, parentDomain, forApp, renderOptions);
1736
1843
  }
1737
1844
  if (child.nodeName === "#text") {
1738
1845
  text(child, forApp);
@@ -2203,6 +2310,6 @@ function getPostBodySummary(obj, length, platform) {
2203
2310
  return res;
2204
2311
  }
2205
2312
 
2206
- export { IMAGE_SIZES, SECTION_LIST, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
2313
+ export { IMAGE_SIZES, SECTION_LIST, buildPictureSources, buildSrcSet, buildSrcSetForFormat, catchPostImage, getEntryImageRawUrl, isAllowedEmbedSrc, isPictureEligibleRawUrl, isValidPermlink, getPostBodySummary as postBodySummary, proxifyImageSrc, markdown2Html as renderPostBody, setCacheSize, setProxyBase, setSlowRenderThresholdMs, simpleMarkdownToHTML };
2207
2314
  //# sourceMappingURL=index.mjs.map
2208
2315
  //# sourceMappingURL=index.mjs.map