Update KefinTweaks-plugin to properly support switching to Experimental branch based on new changes

This commit is contained in:
HighImKevin committed 2026-09-15 06:00:36 -04:00
1 parent 47f4b4a7bd
commit 0c5d5f603a
1 file changed
+279 -20
+279 -20
View File
@@ -186,7 +186,7 @@
});
}
// Save configuration to JavaScript Injector
// Save configuration to JavaScript Injector (Config + KefinTweaks-injector preload)
async function saveConfigToJavaScriptInjector(config) {
try {
const pluginId = await findJavaScriptInjectorPlugin();
@@ -202,19 +202,57 @@
window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
const existingScriptIndex = injectorConfig.CustomJavaScripts.findIndex(
script => script.Name === 'KefinTweaks-Config'
);
let injectorScriptContent = null;
try {
const rootRaw = config.kefinTweaksRoot || '';
const resolvedRaw = config.kefinTweaksRootResolved || rootRaw;
const root = resolvedRaw.endsWith('/') ? resolvedRaw : resolvedRaw + '/';
if (root && root !== '/') {
await loadKefinTweaksLoaderFromRoot(root);
const Loader = window.KefinTweaksLoader;
if (Loader) {
Loader.ensureKefinTweaksApi(Loader.SCRIPT_DEFINITIONS);
let major = null;
try {
major = await window.KefinTweaks.getJellyfinMajorVersion();
} catch (e) {
console.warn('[KefinTweaks Installer] Could not resolve Jellyfin major for injector bake:', e);
}
const planRoot = Loader.getResolvedKefinRoot
? Loader.getResolvedKefinRoot(config)
: root;
const plan = Loader.buildLoadPlan(config, major, {
root: planRoot,
configOnly: config.enabled === false
});
injectorScriptContent = Loader.buildInjectorScript(plan);
console.log('[KefinTweaks Installer] Built KefinTweaks-injector with', plan.assets.length, 'assets');
}
}
} catch (e) {
console.warn('[KefinTweaks Installer] Failed to build KefinTweaks-injector:', e);
}
if (existingScriptIndex !== -1) {
injectorConfig.CustomJavaScripts[existingScriptIndex].Script = scriptContent;
if (window.KefinTweaksLoader) {
injectorConfig.CustomJavaScripts = window.KefinTweaksLoader.upsertInjectorEntries(
injectorConfig.CustomJavaScripts,
{ configScriptContent: scriptContent, injectorScriptContent }
);
} else {
injectorConfig.CustomJavaScripts.push({
Name: 'KefinTweaks-Config',
Script: scriptContent,
Enabled: true,
RequiresAuthentication: false
});
const existingScriptIndex = injectorConfig.CustomJavaScripts.findIndex(
script => script.Name === 'KefinTweaks-Config'
);
if (existingScriptIndex !== -1) {
injectorConfig.CustomJavaScripts[existingScriptIndex].Script = scriptContent;
injectorConfig.CustomJavaScripts[existingScriptIndex].Enabled = true;
} else {
injectorConfig.CustomJavaScripts.push({
Name: 'KefinTweaks-Config',
Script: scriptContent,
Enabled: true,
RequiresAuthentication: false
});
}
}
const server = ApiClient._serverAddress;
@@ -239,6 +277,157 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
}
}
function loadKefinTweaksLoaderFromRoot(root) {
const normalized = root.endsWith('/') ? root : root + '/';
const url = `${normalized}scripts/kefinTweaks-loader.js`;
if (window.KefinTweaksLoader) return Promise.resolve(window.KefinTweaksLoader);
return new Promise((resolve, reject) => {
const existing = document.querySelector(`script[src="${url}"]`);
if (existing) {
const start = Date.now();
const wait = () => {
if (window.KefinTweaksLoader) return resolve(window.KefinTweaksLoader);
if (Date.now() - start > 15000) return reject(new Error('Timed out waiting for KefinTweaksLoader'));
setTimeout(wait, 50);
};
return wait();
}
const script = document.createElement('script');
script.src = url;
script.async = false;
script.onload = () => resolve(window.KefinTweaksLoader);
script.onerror = () => reject(new Error('Failed to load ' + url));
document.head.appendChild(script);
});
}
function isLoggedInForInjectorWrite() {
try {
return !!(window.ApiClient
&& window.ApiClient._loggedIn
&& typeof window.ApiClient.accessToken === 'function'
&& window.ApiClient.accessToken()
&& window.ApiClient._serverAddress);
} catch (e) {
return false;
}
}
async function waitForLoginForInjectorWrite(maxWaitMs = 10000, checkInterval = 250) {
if (isLoggedInForInjectorWrite()) return true;
const start = Date.now();
while (Date.now() - start < maxWaitMs) {
await new Promise((r) => setTimeout(r, checkInterval));
if (isLoggedInForInjectorWrite()) return true;
}
return false;
}
/**
* Create/update KefinTweaks-injector (and Config) in JS Injector when missing.
* @returns {Promise<{ ok: boolean, plan?: object, reason?: string }>}
*/
async function ensureKefinTweaksInjectorEntry(config) {
const rootRaw = config?.kefinTweaksRoot || '';
if (!rootRaw) {
return { ok: false, reason: 'kefinTweaksRoot not configured' };
}
if (!isLoggedInForInjectorWrite()) {
return { ok: false, reason: 'not logged in' };
}
try {
const normalize = (r) => {
if (!r || typeof r !== 'string') return '';
return r.endsWith('/') ? r : r + '/';
};
const expected = normalize(await resolveRootVersion(rootRaw));
const current = normalize(config.kefinTweaksRootResolved || '');
if (!current || current !== expected) {
config.kefinTweaksRootResolved = expected;
console.log('[KefinTweaks Installer] Updated kefinTweaksRootResolved:', current || '(none)', '->', expected);
}
const loadRoot = normalize(config.kefinTweaksRootResolved || rootRaw);
await loadKefinTweaksLoaderFromRoot(loadRoot);
const Loader = window.KefinTweaksLoader;
if (!Loader) {
return { ok: false, reason: 'KefinTweaksLoader unavailable' };
}
Loader.ensureKefinTweaksApi(Loader.SCRIPT_DEFINITIONS);
let major = null;
try {
major = await window.KefinTweaks.getJellyfinMajorVersion();
} catch (e) {
console.warn('[KefinTweaks Installer] Could not resolve Jellyfin major for auto-create:', e);
}
const resolvedRoot = Loader.getResolvedKefinRoot(config);
const plan = Loader.buildLoadPlan(config, major, {
root: resolvedRoot,
configOnly: config.enabled === false
});
const injectorScriptContent = Loader.buildInjectorScript(plan);
const configScriptContent = `// KefinTweaks Configuration
// This file is automatically generated by KefinTweaks Installer
// Do not edit manually unless you know what you're doing
window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
const pluginId = await findJavaScriptInjectorPlugin();
if (!pluginId) {
return { ok: false, reason: 'JavaScript Injector plugin not found' };
}
const injectorConfig = await getJavaScriptInjectorConfig(pluginId);
if (!injectorConfig) {
return { ok: false, reason: 'Could not read JS Injector configuration' };
}
if (!injectorConfig.CustomJavaScripts) {
injectorConfig.CustomJavaScripts = [];
}
injectorConfig.CustomJavaScripts = Loader.upsertInjectorEntries(
injectorConfig.CustomJavaScripts,
{ configScriptContent, injectorScriptContent }
);
const server = ApiClient._serverAddress;
const response = await fetch(`${server}/Plugins/${pluginId}/Configuration`, {
method: 'POST',
headers: {
'Authorization': getAuthHeader(),
'Content-Type': 'application/json'
},
body: JSON.stringify(injectorConfig)
});
if (!response.ok) {
return {
ok: false,
reason: `POST failed HTTP ${response.status}: ${response.statusText}`
};
}
console.log('[KefinTweaks Installer] Auto-created KefinTweaks-injector with', plan.assets.length, 'assets');
return { ok: true, plan };
} catch (err) {
console.warn('[KefinTweaks Installer] ensureKefinTweaksInjectorEntry failed:', err);
return { ok: false, reason: err?.message || String(err) };
}
}
function hasEnabledKefinInjectorEntry(customJavaScripts) {
if (window.KefinTweaksLoader?.hasEnabledInjectorEntry) {
return window.KefinTweaksLoader.hasEnabledInjectorEntry(customJavaScripts);
}
return (customJavaScripts || []).some(
(s) => s.Name === 'KefinTweaks-injector' && s.Enabled !== false
);
}
const LATEST_RELEASE_NAME = 'Latest';
const DEVELOPMENT_NAME = 'Development';
const EXPERIMENTAL_NAME = 'Experimental';
@@ -726,6 +915,10 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
}
const kefinTweaksRoot = buildKefinTweaksRootUrl(sourceType, source);
const kefinTweaksRootResolvedRaw = await resolveRootVersion(kefinTweaksRoot);
const kefinTweaksRootResolved = kefinTweaksRootResolvedRaw.endsWith('/')
? kefinTweaksRootResolvedRaw
: kefinTweaksRootResolvedRaw + '/';
// Load default config from the selected root
let defaultConfig;
@@ -736,6 +929,7 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
// Create minimal config if loading fails
defaultConfig = {
kefinTweaksRoot: kefinTweaksRoot,
kefinTweaksRootResolved: kefinTweaksRootResolved,
enabled: true,
scripts: {},
exclusiveElsewhere: {},
@@ -757,6 +951,7 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
kefinTweaksRoot: kefinTweaksRoot,
...(currentConfig || {}),
kefinTweaksRoot: kefinTweaksRoot, // Ensure root is updated
kefinTweaksRootResolved: kefinTweaksRootResolved,
enabled: enabledState // Update enabled state
};
@@ -805,7 +1000,20 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
versionDisplay = sourceInfo.version;
}
// Load injector.js from the configured root
// Always load live injector.js for plan apply + stamp sync (baked preload may be stale)
let hasPreload = false;
try {
const pluginId = await findJavaScriptInjectorPlugin();
const injCfg = await getJavaScriptInjectorConfig(pluginId);
hasPreload = (injCfg.CustomJavaScripts || []).some(
(s) => s.Name === 'KefinTweaks-injector' && s.Enabled !== false
);
} catch (e) {
console.warn('[KefinTweaks Installer] Could not verify KefinTweaks-injector after save:', e);
}
if (hasPreload) {
console.log('[KefinTweaks Installer] KefinTweaks-injector saved; still loading injector.js for live plan + sync');
}
await loadInjectorFromRoot(kefinTweaksRoot);
// Show success modal
@@ -885,6 +1093,17 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
console.log('[KefinTweaks Installer] Renamed KefinTweaks-Config to backup and disabled it');
}
// Also disable/rename KefinTweaks-injector preload entry
const injectorScriptIndex = injectorConfig.CustomJavaScripts.findIndex(
script => script.Name === 'KefinTweaks-injector'
);
if (injectorScriptIndex !== -1) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
injectorConfig.CustomJavaScripts[injectorScriptIndex].Name = `KefinTweaks-injector-Backup-${timestamp}`;
injectorConfig.CustomJavaScripts[injectorScriptIndex].Enabled = false;
console.log('[KefinTweaks Installer] Renamed KefinTweaks-injector to backup and disabled it');
}
// Step 2: Find and disable installer script
// Look for script containing kefinTweaks-plugin.js (flexible pattern to match script.src*kefinTweaks-plugin.js)
const installerScriptIndex = injectorConfig.CustomJavaScripts.findIndex(
@@ -1625,7 +1844,7 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
});
}
// Check if config exists and load injector.js if ready
// Check if config exists; auto-create KefinTweaks-injector when missing; always load live injector.js
async function checkAndLoadInjector() {
try {
const config = await getKefinTweaksConfig();
@@ -1633,12 +1852,53 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
// Before injector/utils: hide fallback page chrome for custom routes
injectCustomPageStyles(config);
if (root && root !== '') {
await loadInjectorFromRoot(root);
} else {
if (!root || root === '') {
console.log('[KefinTweaks Installer] kefinTweaksRoot not configured. Please configure via the Plugins page.');
return;
}
// Plugin config GET/POST needs a session; wait briefly so admin first-load can auto-create
await waitForLoginForInjectorWrite();
let injectorConfig = null;
try {
const pluginId = await findJavaScriptInjectorPlugin();
if (pluginId) {
injectorConfig = await getJavaScriptInjectorConfig(pluginId);
}
} catch (e) {
console.warn('[KefinTweaks Installer] Could not read JS Injector config:', e);
}
const hasPreload = !!(injectorConfig && hasEnabledKefinInjectorEntry(injectorConfig.CustomJavaScripts));
if (hasPreload) {
console.log('[KefinTweaks Installer] KefinTweaks-injector present; still loading injector.js for live plan + sync');
} else {
// Entry missing: try to create + apply assets now (admin/logged-in)
const ensured = await ensureKefinTweaksInjectorEntry(config);
if (ensured.ok && ensured.plan) {
window.KefinTweaksScriptsPreloaded = true;
if (window.KefinTweaksLoader) {
window.KefinTweaksLoader.ensureKefinTweaksApi(window.KefinTweaksLoader.SCRIPT_DEFINITIONS);
window.KefinTweaksLoader.applyAssetsToDocument(ensured.plan.assets);
}
try {
document.dispatchEvent(new CustomEvent('kefinTweaksLoaded', {
detail: {
stamp: ensured.plan.stamp,
timestamp: new Date().toISOString(),
autoCreated: true
}
}));
} 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');
}
}
await loadInjectorFromRoot(root);
} catch (error) {
console.error('[KefinTweaks Installer] Error checking config:', error);
}
@@ -1654,5 +1914,4 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
}
console.log('[KefinTweaks Installer] Installer script loaded');
})();
})();