/* global chrome, browser */
if (typeof browser === "undefined" && typeof chrome !== "undefined") {
browser = chrome;
}
const PROMPT_HOST_ID = "__trackeepYoutubePromptHost";
let lastNotifiedVideoId = "";
let lastUrl = "";
function parseYouTubeVideo(url, title) {
let parsed;
try {
parsed = new URL(url);
} catch (_) {
return null;
}
const host = parsed.hostname.toLowerCase();
const path = parsed.pathname || "";
let videoId = "";
if (host === "youtu.be") {
videoId = path.slice(1).split("/")[0];
} else if (path.startsWith("/watch")) {
videoId = parsed.searchParams.get("v") || "";
} else if (path.startsWith("/shorts/")) {
videoId = path.split("/")[2] || "";
} else if (path.startsWith("/embed/")) {
videoId = path.split("/")[2] || "";
}
if (!videoId) {
return null;
}
const cleanedTitle = (title || "").replace(/\s*-\s*YouTube$/i, "").trim();
return {
videoId,
url: `https://www.youtube.com/watch?v=${videoId}`,
title: cleanedTitle || "YouTube video",
};
}
function sendMessage(message, callback) {
try {
browser.runtime.sendMessage(message, callback);
} catch (_) {
if (typeof callback === "function") {
callback(undefined);
}
}
}
function closePrompt() {
const existing = document.getElementById(PROMPT_HOST_ID);
if (existing) {
existing.remove();
}
}
function renderPrompt(video) {
closePrompt();
const host = document.createElement("div");
host.id = PROMPT_HOST_ID;
const shadow = host.attachShadow({ mode: "open" });
shadow.innerHTML = `
Trackeep ยท YouTube
Save this video to your Trackeep?
${escapeHtml(video.title)}
`;
document.documentElement.appendChild(host);
const saveBtn = shadow.getElementById("saveNow");
const openBtn = shadow.getElementById("openSaver");
const laterBtn = shadow.getElementById("later");
const statusEl = shadow.getElementById("status");
const setStatus = (message, type) => {
statusEl.textContent = message || "";
statusEl.className = `status${type ? ` ${type}` : ""}`;
};
saveBtn.addEventListener("click", () => {
saveBtn.disabled = true;
openBtn.disabled = true;
laterBtn.disabled = true;
setStatus("Saving to Trackeep...", "");
sendMessage({ type: "trackeep:youtube-save-request", video }, (response) => {
if (browser.runtime && browser.runtime.lastError) {
setStatus("Could not reach extension background worker.", "error");
} else if (response && response.ok) {
setStatus("Saved successfully.", "ok");
setTimeout(closePrompt, 1200);
} else {
setStatus(
response && response.error
? response.error
: "Save failed. Open saver to review.",
"error"
);
saveBtn.disabled = false;
openBtn.disabled = false;
laterBtn.disabled = false;
}
});
});
openBtn.addEventListener("click", () => {
sendMessage({ type: "trackeep:youtube-open-saver", video }, () => {
closePrompt();
});
});
laterBtn.addEventListener("click", () => {
sendMessage({ type: "trackeep:youtube-dismissed", videoId: video.videoId }, () => {
closePrompt();
});
});
window.setTimeout(() => {
closePrompt();
}, 18000);
}
function escapeHtml(value) {
return String(value || "")
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function detectAndNotify(force) {
const video = parseYouTubeVideo(window.location.href, document.title);
if (!video) {
closePrompt();
return;
}
if (!force && video.videoId === lastNotifiedVideoId && window.location.href === lastUrl) {
return;
}
lastNotifiedVideoId = video.videoId;
lastUrl = window.location.href;
sendMessage({ type: "trackeep:youtube-video-detected", video }, (response) => {
if (browser.runtime && browser.runtime.lastError) {
return;
}
if (response && response.showPrompt) {
renderPrompt(video);
} else {
closePrompt();
}
});
}
function initDetection() {
let previousHref = window.location.href;
const check = () => {
if (window.location.href !== previousHref) {
previousHref = window.location.href;
detectAndNotify(true);
}
};
window.addEventListener("yt-navigate-finish", () => detectAndNotify(true), true);
window.addEventListener("popstate", () => detectAndNotify(true), true);
document.addEventListener("visibilitychange", () => {
if (!document.hidden) {
detectAndNotify(false);
}
});
const observer = new MutationObserver(check);
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
window.setInterval(check, 1200);
detectAndNotify(true);
}
initDetection();