Force refresh after background update and avoid duplicate injections

This commit is contained in:
HighImKevin committed 2026-10-16 08:04:52 -04:00
1 parent 51348f5ce2
commit ed49848f1d
2 files changed
+226 -51

No files matched your search

+39 -11
View File
@@ -687,18 +687,27 @@
//const urlSuffix = `?v=${new Date().getTime()}`;
// Inject dashboard drawer version badge CSS using dynamic version number
function isFloatingKefinRootUrl(root) {
return /@(latest|main|master|experimental)(\/|$)/i.test(String(root || ''));
}
async function injectVersionBadgeCSS() {
const styleId = 'kefinTweaks-version-css';
if (document.getElementById(styleId)) return;
// Get version from root URL in config
// Prefer pinned resolved root; GitHub resolve only for admins on floating symbolic roots
let displayVersion = "";
try {
const configRoot = window.KefinTweaksConfig?.kefinTweaksRoot || '';
const cfg = window.KefinTweaksConfig || {};
let configRoot = cfg.kefinTweaksRootResolved || cfg.kefinTweaksRoot || '';
if (configRoot) {
// Resolve the root to get the actual version (if @latest or @main)
const resolvedRoot = await resolveRootVersion(configRoot);
displayVersion = extractVersionFromRoot(resolvedRoot);
if (isFloatingKefinRootUrl(configRoot)) {
const isAdmin = await checkAdminWithTimeout(2000);
if (isAdmin) {
configRoot = await resolveRootVersion(configRoot);
}
}
displayVersion = extractVersionFromRoot(configRoot);
}
} catch (error) {
console.warn('[KefinTweaks Injector] Could not extract version, using fallback:', error);
@@ -870,10 +879,12 @@
}
// Get the root path for scripts from configuration
// Prefer kefinTweaksRootResolved; GitHub floating resolve only for admins.
async function getScriptRoot() {
const kefinTweaksRoot = window.KefinTweaksConfig?.kefinTweaksRoot;
const cfg = window.KefinTweaksConfig || {};
const kefinTweaksRoot = cfg.kefinTweaksRootResolved || cfg.kefinTweaksRoot;
// If kefinTweaksRoot is not set or empty, return null (scripts should not load)
// If root is not set or empty, return null (scripts should not load)
if (!kefinTweaksRoot || kefinTweaksRoot === '') {
console.warn('[KefinTweaks Injector] kefinTweaksRoot is not configured');
return null;
@@ -882,9 +893,14 @@
// Ensure root ends with /
let root = kefinTweaksRoot.endsWith('/') ? kefinTweaksRoot : kefinTweaksRoot + '/';
// Resolve @latest, @main, or @experimental if needed (with caching)
// Resolve floating refs only when still floating and user is admin (with caching)
if (resolvedRootCache === null) {
root = await resolveRootVersion(root);
if (isFloatingKefinRootUrl(root)) {
const isAdmin = await checkAdminWithTimeout(2000);
if (isAdmin) {
root = await resolveRootVersion(root);
}
}
resolvedRootCache = root;
} else {
root = resolvedRootCache;
@@ -1185,6 +1201,11 @@
async function initialize() {
console.log('[KefinTweaks Injector] Starting KefinTweaks initialization...');
const bakeAlreadyAttached = !!(
window.KefinTweaksScriptsPreloaded
|| window.KefinTweaksInjectorAttached
);
const scriptRoot = await getScriptRoot();
if (!scriptRoot) {
console.log('[KefinTweaks Injector] kefinTweaksRoot is not configured. Please configure KefinTweaks using the installer before scripts can be loaded.');
@@ -1206,7 +1227,7 @@
const config = window.KefinTweaksConfig || {};
const root = Loader.getResolvedKefinRoot
? Loader.getResolvedKefinRoot(config)
: Loader.normalizeRoot(config.kefinTweaksRoot || '');
: Loader.normalizeRoot(config.kefinTweaksRootResolved || config.kefinTweaksRoot || '');
try {
const plan = Loader.buildLoadPlan(config, majorVersion, {
@@ -1214,8 +1235,15 @@
urlSuffix,
configOnly: config.enabled === false
});
if (bakeAlreadyAttached) {
// Belt-and-suspenders: never dual-inject when bake already ran.
// Tip re-pin / bake rewrite is owned by kefinTweaks-plugin tip-sync path.
console.log('[KefinTweaks Injector] Bake already attached; skipping applyAssetsToDocument (stamp=', plan.stamp, ')');
return;
}
console.log('[KefinTweaks Injector] Applying ordered assets:', plan.assets.length, 'stamp=', plan.stamp);
// Dedupe against tags already injected by baked KefinTweaks-injector
Loader.applyAssetsToDocument(plan.assets);
document.dispatchEvent(new CustomEvent('kefinTweaksLoaded', {
detail: {
+187 -40
View File
@@ -367,7 +367,9 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
if (typeof window.KefinTweaksToaster?.toast !== 'function') return false;
window.__kefinPendingRootUpdateToast = null;
try {
window.KefinTweaksToaster.toast(pending.message, '5');
// Sticky when a forced tip-reload countdown is active; otherwise auto-hide.
const sticky = !!pending.sticky || !!window.__kefinTipReloadScheduled;
window.KefinTweaksToaster.toast(pending.message, pending.duration || '5', !sticky);
} catch (e) {
console.warn('[KefinTweaks Installer] Failed to show root-update toast:', e);
return false;
@@ -375,9 +377,13 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
return true;
}
function queueRootUpdateToast(message) {
function queueRootUpdateToast(message, options = null) {
if (!message) return;
window.__kefinPendingRootUpdateToast = { message };
window.__kefinPendingRootUpdateToast = {
message,
sticky: !!(options && options.sticky),
duration: (options && options.duration) || '5'
};
if (flushPendingRootUpdateToast()) return;
const start = Date.now();
const poll = () => {
@@ -389,7 +395,41 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
}
/**
* Queue toast after a floating root's resolved URL was persisted (skip first pin).
* After tip re-pin is persisted to disk, force reload so Config/bake/scripts stay aligned.
* Single-flight per page session.
*/
function scheduleForcedTipReloadCountdown({ symbolicRoot, nextResolved }) {
if (window.__kefinTipReloadScheduled) return;
window.__kefinTipReloadScheduled = true;
const label = formatResolvedUpdateLabel(symbolicRoot, nextResolved);
let remaining = 5;
const show = (n) => {
queueRootUpdateToast(
`KefinTweaks updated to ${label} — refreshing in ${n}…`,
{ sticky: true, duration: '6' }
);
};
show(remaining);
const tick = setInterval(() => {
remaining -= 1;
if (remaining <= 0) {
clearInterval(tick);
try {
window.location.reload();
} catch (e) {
console.warn('[KefinTweaks Installer] Reload after tip update failed:', e);
}
return;
}
show(remaining);
}, 1000);
}
/**
* After a floating root's resolved URL was persisted (skip first pin): countdown + reload.
*/
function maybeQueueResolvedRootUpdateToast({ symbolicRoot, previousResolved, nextResolved, persisted }) {
if (!persisted) return;
@@ -397,8 +437,78 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
const previous = normalizeKefinRootUrl(previousResolved);
const next = normalizeKefinRootUrl(nextResolved);
if (!previous || !next || previous === next) return;
const label = formatResolvedUpdateLabel(symbolicRoot, next);
queueRootUpdateToast(`KefinTweaks updated to ${label}`);
scheduleForcedTipReloadCountdown({ symbolicRoot, nextResolved: next });
}
/**
* Admin + bake already on page: tip resolve/persist/sync without loading live injector.js.
* Tip advanced → persist Config+bake then forced reload. Tip unchanged → stamp sync only.
*/
async function handleAdminAttachedBakeTipSync(config) {
const symbolic = config?.kefinTweaksRoot || '';
if (!symbolic) return;
await waitForLoginForInjectorWrite();
const current = normalizeKefinRootUrl(config.kefinTweaksRootResolved || '');
let tip = normalizeKefinRootUrl(symbolic);
if (isFloatingKefinRoot(symbolic)) {
tip = normalizeKefinRootUrl(await resolveRootVersion(symbolic));
}
if (tip && tip !== current) {
console.log('[KefinTweaks Installer] Tip advanced while bake attached:', current || '(none)', '->', tip);
config.kefinTweaksRootResolved = tip;
if (!window.KefinTweaksConfig) {
window.KefinTweaksConfig = config;
} else {
window.KefinTweaksConfig.kefinTweaksRootResolved = tip;
}
try {
const persisted = await saveConfigToJavaScriptInjector(config);
if (persisted) {
scheduleForcedTipReloadCountdown({ symbolicRoot: symbolic, nextResolved: tip });
console.log('[KefinTweaks Installer] Tip persist+bake ok; scheduling reload');
} else {
console.warn('[KefinTweaks Installer] Tip advanced but persist failed; not reloading');
}
} catch (e) {
console.error('[KefinTweaks Installer] Tip persist failed; not reloading:', e);
}
return;
}
// Tip unchanged — stamp sync only (no live inject, no reload)
try {
const loadRoot = current || tip;
if (!loadRoot) return;
await loadKefinTweaksLoaderFromRoot(loadRoot);
const Loader = window.KefinTweaksLoader;
if (!Loader || typeof Loader.syncKefinTweaksInjector !== 'function') {
console.log('[KefinTweaks Installer] Loader sync unavailable; bake attached, tip unchanged');
return;
}
Loader.ensureKefinTweaksApi(Loader.SCRIPT_DEFINITIONS);
let major = null;
try {
major = await window.KefinTweaks.getJellyfinMajorVersion();
} catch (e) { /* ignore */ }
const planRoot = Loader.getResolvedKefinRoot
? Loader.getResolvedKefinRoot(config)
: loadRoot;
const plan = Loader.buildLoadPlan(config, major, {
root: planRoot,
configOnly: config.enabled === false
});
const syncResult = await Loader.syncKefinTweaksInjector(plan);
console.log(
'[KefinTweaks Installer] Attached bake stamp sync:',
syncResult?.synced ? ('synced ' + syncResult.stamp) : (syncResult?.reason || 'done')
);
} catch (e) {
console.warn('[KefinTweaks Installer] Attached bake stamp sync failed:', e);
}
}
function isKefinInjectorAttachedOnPage() {
@@ -2055,50 +2165,76 @@ main.MuiBox-root .customPage.libraryPage:not(.noSecondaryNavPage)[data-kefin-cus
return root;
}
// Load injector.js from a specific root URL (resolves floating refs first)
// Load injector.js from a specific root URL (fallback when bake is absent).
// GitHub floating resolve + persist only for admins; non-admins use pinned/symbolic as-is.
async function loadInjectorFromRoot(root) {
if (!root || root === '') {
console.warn('[KefinTweaks Installer] Cannot load injector: kefinTweaksRoot is empty');
return false;
}
// Ensure root ends with /
const rootUrl = root.endsWith('/') ? root : root + '/';
const resolvedRoot = await resolveRootVersion(rootUrl);
const injectorUrl = `${resolvedRoot}injector.js`;
const rootUrl = normalizeKefinRootUrl(root);
const admin = await isAdmin();
let resolvedRoot = rootUrl;
if (isFloatingKefinRoot(rootUrl)) {
if (admin) {
resolvedRoot = normalizeKefinRootUrl(await resolveRootVersion(rootUrl));
} else {
const pinned = normalizeKefinRootUrl(window.KefinTweaksConfig?.kefinTweaksRootResolved || '');
resolvedRoot = pinned || rootUrl;
console.log('[KefinTweaks Installer] Non-admin: skipping GitHub resolve; using', resolvedRoot);
}
}
if (resolvedRoot !== rootUrl) {
console.log('[KefinTweaks Installer] Resolved root from', rootUrl, 'to', resolvedRoot);
}
// Persist commit-SHA / release-tag root when floating GitHub refs resolve to a new URL
try {
const expected = normalizeKefinRootUrl(resolvedRoot);
if (!window.KefinTweaksConfig) {
window.KefinTweaksConfig = {};
}
const symbolicRoot = window.KefinTweaksConfig.kefinTweaksRoot || rootUrl;
const current = normalizeKefinRootUrl(window.KefinTweaksConfig.kefinTweaksRootResolved || '');
if (expected && expected !== current) {
window.KefinTweaksConfig.kefinTweaksRootResolved = expected;
console.log('[KefinTweaks Installer] Updated kefinTweaksRootResolved:', current || '(none)', '->', expected);
// Only admins can GET/POST /Plugins — never attempt persist for non-admins
if (isLoggedInForInjectorWrite() && await isAdmin()) {
await saveConfigToJavaScriptInjector(window.KefinTweaksConfig);
maybeQueueResolvedRootUpdateToast({
symbolicRoot,
previousResolved: current,
nextResolved: expected,
persisted: true
});
} else if (!isLoggedInForInjectorWrite()) {
console.warn('[KefinTweaks Installer] kefinTweaksRootResolved updated in memory; not logged in to persist');
// Admin: persist tip move; if replacing a previous pin, countdown reload (no tip injector this session)
if (admin && isFloatingKefinRoot(rootUrl)) {
try {
const expected = normalizeKefinRootUrl(resolvedRoot);
if (!window.KefinTweaksConfig) {
window.KefinTweaksConfig = {};
}
const symbolicRoot = window.KefinTweaksConfig.kefinTweaksRoot || rootUrl;
const current = normalizeKefinRootUrl(window.KefinTweaksConfig.kefinTweaksRootResolved || '');
if (expected && expected !== current) {
window.KefinTweaksConfig.kefinTweaksRootResolved = expected;
console.log('[KefinTweaks Installer] Updated kefinTweaksRootResolved:', current || '(none)', '->', expected);
if (isLoggedInForInjectorWrite()) {
const persisted = await saveConfigToJavaScriptInjector(window.KefinTweaksConfig);
if (persisted && current) {
// Prior pin existed — force reload so scripts/Config stay aligned
maybeQueueResolvedRootUpdateToast({
symbolicRoot,
previousResolved: current,
nextResolved: expected,
persisted: true
});
return true;
}
if (persisted && !current) {
// First pin: continue and load injector from the new resolved URL
console.log('[KefinTweaks Installer] First resolved pin persisted; loading injector.js');
}
} else {
console.warn('[KefinTweaks Installer] kefinTweaksRootResolved updated in memory; not logged in to persist');
}
}
} catch (e) {
console.warn('[KefinTweaks Installer] Failed to persist kefinTweaksRootResolved:', e);
}
} catch (e) {
console.warn('[KefinTweaks Installer] Failed to persist kefinTweaksRootResolved:', e);
}
if (window.__kefinTipReloadScheduled) {
console.log('[KefinTweaks Installer] Tip reload scheduled; skipping injector.js load');
return true;
}
const injectorUrl = `${resolvedRoot}injector.js`;
// Check if injector is already loaded from this URL
const existingScript = document.querySelector(`script[src="${injectorUrl}"]`);
if (existingScript) {
@@ -2127,7 +2263,7 @@ main.MuiBox-root .customPage.libraryPage:not(.noSecondaryNavPage)[data-kefin-cus
});
}
// Check if config exists; auto-create KefinTweaks-injector when missing (admin only); always load live injector.js
// Check if config exists; auto-create bake when missing (admin); never dual-inject when bake attached
async function checkAndLoadInjector() {
try {
// Prefer in-page Config (already injected). Avoid /Plugins GET on normal page load for non-admins.
@@ -2149,9 +2285,18 @@ main.MuiBox-root .customPage.libraryPage:not(.noSecondaryNavPage)[data-kefin-cus
return;
}
// Bake already on page: never load live injector.js (prevents dual SHA trees)
if (isKefinInjectorAttachedOnPage()) {
console.log('[KefinTweaks Installer] KefinTweaks-injector present on page; still loading injector.js for live plan + sync');
} else if (await isAdmin()) {
if (!(await isAdmin())) {
console.log('[KefinTweaks Installer] KefinTweaks-injector attached; non-admin skipping live injector.js');
return;
}
console.log('[KefinTweaks Installer] KefinTweaks-injector attached; admin tip sync without live inject');
await handleAdminAttachedBakeTipSync(config);
return;
}
if (await isAdmin()) {
await waitForLoginForInjectorWrite();
const ensured = await ensureKefinTweaksInjectorEntry(config);
if (ensured.ok && ensured.plan) {
@@ -2171,9 +2316,11 @@ main.MuiBox-root .customPage.libraryPage:not(.noSecondaryNavPage)[data-kefin-cus
}));
} catch (e) { /* ignore */ }
console.log('[KefinTweaks Installer] Applied KefinTweaks-injector assets after auto-create');
} else {
console.log('[KefinTweaks Installer] KefinTweaks-injector not available (' + (ensured.reason || 'unknown') + '); loading injector.js');
// Tip advance inside ensure may have scheduled reload; never also load tip injector.js
if (window.__kefinTipReloadScheduled) return;
return;
}
console.log('[KefinTweaks Installer] KefinTweaks-injector not available (' + (ensured.reason || 'unknown') + '); loading injector.js');
} else {
console.log('[KefinTweaks Installer] KefinTweaks-injector not on page (non-admin); loading injector.js');
}