Refactor shared helpers into utility modules

This commit is contained in:
thePR0M3TH3AN committed 2025-10-02 21:33:56 -04:00
1 parent 245f6bd0d1
commit 2e6e7012a6
5 files changed
+179 -141

No files matched your search

+17 -130
View File
@@ -52,22 +52,20 @@ import {
formatViewCount,
ingestLocalViewEvent,
} from "./viewCounter.js";
function truncateMiddle(text, maxLength = 72) {
if (!text || typeof text !== "string") {
return "";
}
if (text.length <= maxLength) {
return text;
}
const ellipsis = "…";
const charsToShow = maxLength - ellipsis.length;
const front = Math.ceil(charsToShow / 2);
const back = Math.floor(charsToShow / 2);
return `${text.slice(0, front)}${ellipsis}${text.slice(text.length - back)}`;
}
import {
formatAbsoluteTimestamp as formatAbsoluteTimestampUtil,
formatTimeAgo as formatTimeAgoUtil,
truncateMiddle,
} from "./utils/formatters.js";
import {
escapeHTML as escapeHtml,
removeTrackingScripts,
} from "./utils/domUtils.js";
import {
readUrlHealthFromStorage,
removeUrlHealthFromStorage,
writeUrlHealthToStorage,
} from "./utils/storage.js";
function pointerArrayToKey(pointer) {
if (!Array.isArray(pointer) || pointer.length < 2) {
@@ -109,7 +107,6 @@ const ADMIN_DM_IMAGE_URL =
const BITVID_WEBSITE_URL = "https://bitvid.network/";
const MAX_DISCUSSION_COUNT_VIDEOS = 24;
const VIDEO_EVENT_KIND = 30078;
const TRACKING_SCRIPT_PATTERN = /(?:^|\/)tracking\.js(?:$|\?)/;
const EMPTY_VIDEO_LIST_SIGNATURE = "__EMPTY__";
/**
* Local storage keys for cached profile metadata and saved authentication
@@ -134,76 +131,9 @@ const URL_HEALTH_TTL_MS = 45 * 60 * 1000; // 45 minutes
const URL_HEALTH_TIMEOUT_RETRY_MS = 5 * 60 * 1000; // 5 minutes
const URL_PROBE_TIMEOUT_MS = 8 * 1000; // 8 seconds
const URL_PROBE_TIMEOUT_RETRY_MS = 15 * 1000; // 15 seconds
const URL_HEALTH_STORAGE_PREFIX = "bitvid:urlHealth:";
const urlHealthCache = new Map();
const urlHealthInFlight = new Map();
function removeTrackingScripts(root) {
if (!root || typeof root.querySelectorAll !== "function") {
return;
}
root.querySelectorAll("script[src]").forEach((script) => {
const src = script.getAttribute("src") || "";
if (TRACKING_SCRIPT_PATTERN.test(src)) {
script.remove();
}
});
}
function getUrlHealthStorageKey(eventId) {
return `${URL_HEALTH_STORAGE_PREFIX}${eventId}`;
}
function readUrlHealthFromStorage(eventId) {
if (!eventId || typeof localStorage === "undefined") {
return null;
}
try {
const raw = localStorage.getItem(getUrlHealthStorageKey(eventId));
if (!raw) {
return null;
}
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === "object") {
return parsed;
}
} catch (err) {
console.warn(`Failed to parse stored URL health for ${eventId}:`, err);
}
removeUrlHealthFromStorage(eventId);
return null;
}
function writeUrlHealthToStorage(eventId, entry) {
if (!eventId || typeof localStorage === "undefined") {
return;
}
try {
localStorage.setItem(
getUrlHealthStorageKey(eventId),
JSON.stringify(entry)
);
} catch (err) {
console.warn(`Failed to persist URL health for ${eventId}:`, err);
}
}
function removeUrlHealthFromStorage(eventId) {
if (!eventId || typeof localStorage === "undefined") {
return;
}
try {
localStorage.removeItem(getUrlHealthStorageKey(eventId));
} catch (err) {
console.warn(`Failed to remove URL health for ${eventId}:`, err);
}
}
function isUrlHealthEntryFresh(entry, url) {
if (!entry || typeof entry !== "object") {
return false;
@@ -11528,58 +11458,15 @@ class bitvidApp {
* Format "time ago" for a given timestamp (in seconds).
*/
formatAbsoluteTimestamp(timestamp) {
if (!Number.isFinite(timestamp)) {
return "Unknown date";
}
const date = new Date(timestamp * 1000);
if (Number.isNaN(date.getTime())) {
return "Unknown date";
}
try {
return date.toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
} catch (err) {
return date.toISOString();
}
return formatAbsoluteTimestampUtil(timestamp);
}
formatTimeAgo(timestamp) {
const seconds = Math.floor(Date.now() / 1000 - timestamp);
const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
};
for (const [unit, secInUnit] of Object.entries(intervals)) {
const int = Math.floor(seconds / secInUnit);
if (int >= 1) {
return `${int} ${unit}${int > 1 ? "s" : ""} ago`;
}
}
return "just now";
return formatTimeAgoUtil(timestamp);
}
escapeHTML(unsafe) {
if (unsafe === null || typeof unsafe === "undefined") {
return "";
}
return String(unsafe)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
return escapeHtml(unsafe);
}
showError(msg) {
+1 -11
View File
@@ -9,6 +9,7 @@ import { subscriptions } from "./subscriptions.js"; // <-- NEW import
import { attachHealthBadges } from "./gridHealth.js";
import { attachUrlHealthBadges } from "./urlHealthObserver.js";
import { accessControl } from "./accessControl.js";
import { escapeHTML } from "./utils/domUtils.js";
let currentChannelHex = null;
let currentChannelNpub = null;
@@ -912,14 +913,3 @@ function dedupeToNewestByRoot(videos) {
return Array.from(map.values());
}
/**
* Basic escaping to avoid XSS.
*/
function escapeHTML(unsafe = "") {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
+35
View File
@@ -0,0 +1,35 @@
// js/utils/domUtils.js
const TRACKING_SCRIPT_PATTERN = /(?:^|\/)tracking\.js(?:$|\?)/;
/**
* Removes script tags that match known tracking script patterns.
*/
export function removeTrackingScripts(root) {
if (!root || typeof root.querySelectorAll !== "function") {
return;
}
root.querySelectorAll("script[src]").forEach((script) => {
const src = script.getAttribute("src") || "";
if (TRACKING_SCRIPT_PATTERN.test(src)) {
script.remove();
}
});
}
/**
* Escapes HTML entities in a string to prevent XSS when injecting content.
*/
export function escapeHTML(unsafe) {
if (unsafe === null || typeof unsafe === "undefined") {
return "";
}
return String(unsafe)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
+70
View File
@@ -0,0 +1,70 @@
// js/utils/formatters.js
/**
* Shortens a string by replacing the middle with an ellipsis when it exceeds
* the desired length. Ensures the returned string never exceeds `maxLength`.
*/
export function truncateMiddle(text, maxLength = 72) {
if (!text || typeof text !== "string") {
return "";
}
if (text.length <= maxLength) {
return text;
}
const ellipsis = "…";
const charsToShow = maxLength - ellipsis.length;
const front = Math.ceil(charsToShow / 2);
const back = Math.floor(charsToShow / 2);
return `${text.slice(0, front)}${ellipsis}${text.slice(text.length - back)}`;
}
/**
* Formats a timestamp (in seconds) to a localized string. Falls back to
* ISO-8601 formatting if locale formatting fails.
*/
export function formatAbsoluteTimestamp(timestamp) {
if (!Number.isFinite(timestamp)) {
return "Unknown date";
}
const date = new Date(timestamp * 1000);
if (Number.isNaN(date.getTime())) {
return "Unknown date";
}
try {
return date.toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
} catch (err) {
return date.toISOString();
}
}
/**
* Returns a human readable "time ago" string for a timestamp (in seconds).
*/
export function formatTimeAgo(timestamp) {
const seconds = Math.floor(Date.now() / 1000 - timestamp);
const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
};
for (const [unit, secInUnit] of Object.entries(intervals)) {
const int = Math.floor(seconds / secInUnit);
if (int >= 1) {
return `${int} ${unit}${int > 1 ? "s" : ""} ago`;
}
}
return "just now";
}
+56
View File
@@ -0,0 +1,56 @@
// js/utils/storage.js
const URL_HEALTH_STORAGE_PREFIX = "bitvid:urlHealth:";
export function getUrlHealthStorageKey(eventId) {
return `${URL_HEALTH_STORAGE_PREFIX}${eventId}`;
}
export function readUrlHealthFromStorage(eventId) {
if (!eventId || typeof localStorage === "undefined") {
return null;
}
try {
const raw = localStorage.getItem(getUrlHealthStorageKey(eventId));
if (!raw) {
return null;
}
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === "object") {
return parsed;
}
} catch (err) {
console.warn(`Failed to parse stored URL health for ${eventId}:`, err);
}
removeUrlHealthFromStorage(eventId);
return null;
}
export function writeUrlHealthToStorage(eventId, entry) {
if (!eventId || typeof localStorage === "undefined") {
return;
}
try {
localStorage.setItem(
getUrlHealthStorageKey(eventId),
JSON.stringify(entry)
);
} catch (err) {
console.warn(`Failed to persist URL health for ${eventId}:`, err);
}
}
export function removeUrlHealthFromStorage(eventId) {
if (!eventId || typeof localStorage === "undefined") {
return;
}
try {
localStorage.removeItem(getUrlHealthStorageKey(eventId));
} catch (err) {
console.warn(`Failed to remove URL health for ${eventId}:`, err);
}
}