Files
MajoorWaldi-ComfyUI-Majoor-…/js/app/versionCheck.js
T
MajoorWaldi 1c6653a21d refactor(i18n): remove unused db reset notice and related code
- Remove hardcoded DB reset notice from i18n dictionary
- Remove DB reset notice version check from versionCheck.js
- Delete unused imports and variables related to DB reset notice
2026-04-16 13:55:54 +02:00

327 lines
10 KiB
JavaScript

import { get } from "../api/client.js";
import { ENDPOINTS } from "../api/endpoints.js";
import { comfyToast } from "./toast.js";
import { comfyAlert } from "./dialogs.js";
import { t } from "./i18n.js";
import { SettingsStore } from "./settings/SettingsStore.js";
export const VERSION_UPDATE_EVENT = "mjr:version-update-available";
const VERSION_UPDATE_STATE_KEY = "__MJR_VERSION_UPDATE_STATE__";
const LATEST_RELEASE_URL =
"https://api.github.com/repos/MajoorWaldi/ComfyUI-Majoor-AssetsManager/releases/latest";
const NIGHTLY_RELEASE_URL =
"https://api.github.com/repos/MajoorWaldi/ComfyUI-Majoor-AssetsManager/releases/tags/nightly";
const LAST_CHECK_KEY = "majoor_last_update_check";
const VERSION_TOAST_NOTICE_VERSION_KEY = "majoor_version_toast_notice_version";
const DB_RESET_NOTICE_VERSION_KEY = "majoor_db_reset_notice_version";
const _AI_FEATURES_NOTICE_LOCAL_BUILD_KEY = "majoor_ai_features_notice_local_build";
const NIGHTLY_RELEASE_MARKER_KEY = "majoor_nightly_release_marker";
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
let _versionUpdateState = { available: false, timestamp: Date.now() };
function normalizeVersion(value) {
if (!value) return "";
return String(value).trim().replace(/^v/i, "");
}
/**
* Check whether a version/branch should be treated as nightly/dev build.
*/
function isNightly(version, branch) {
const v = String(version || "")
.trim()
.toLowerCase();
const b = String(branch || "")
.trim()
.toLowerCase();
const nightlyKeywords = ["nightly", "dev", "alpha", "experimental"];
const hasNightlyKeyword = nightlyKeywords.some((kw) => v.includes(kw) || b.includes(kw));
// Examples: 1.2.0+a1b2c3d, 6432abc, deadbeef...
const hasCommitHash = v.includes("+") || (v.length > 10 && /^[a-f0-9]+$/i.test(v));
return hasNightlyKeyword || hasCommitHash;
}
function parseVersionSegments(value) {
return String(value || "")
.split("-")[0]
.split(".")
.map((part) => {
const num = parseInt(part, 10);
return Number.isFinite(num) ? Math.max(0, num) : 0;
});
}
function isNewerVersion(remote, local) {
if (!remote || !local) return false;
const remoteParts = parseVersionSegments(remote);
const localParts = parseVersionSegments(local);
const length = Math.max(remoteParts.length, localParts.length);
for (let i = 0; i < length; i += 1) {
const remoteValue = Number.isFinite(remoteParts[i]) ? remoteParts[i] : 0;
const localValue = Number.isFinite(localParts[i]) ? localParts[i] : 0;
if (remoteValue > localValue) return true;
if (remoteValue < localValue) return false;
}
return false;
}
function createFetchTimeoutSignal(timeoutMs) {
const ms = Math.max(1, Number(timeoutMs) || 10000);
try {
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
return { signal: AbortSignal.timeout(ms), cleanup: () => {} };
}
} catch (e) {
console.debug?.(e);
}
try {
if (typeof AbortController !== "undefined") {
const controller = new AbortController();
const timer = setTimeout(() => {
try {
controller.abort();
} catch (e) {
console.debug?.(e);
}
}, ms);
return {
signal: controller.signal,
cleanup: () => {
try {
clearTimeout(timer);
} catch (e) {
console.debug?.(e);
}
},
};
}
} catch (e) {
console.debug?.(e);
}
return { signal: undefined, cleanup: () => {} };
}
async function fetchReleasePayload(url) {
const { signal, cleanup } = createFetchTimeoutSignal(10_000);
const response = await fetch(url, {
cache: "no-cache",
signal,
headers: {
Accept: "application/vnd.github+json",
},
}).finally(() => cleanup());
if (!response.ok) {
throw new Error(`GitHub release request failed (${response.status})`);
}
const payload = await response.json().catch(() => null);
if (!payload || typeof payload !== "object") {
throw new Error("GitHub release returned invalid payload");
}
return payload;
}
async function fetchLatestReleaseVersion() {
const payload = await fetchReleasePayload(LATEST_RELEASE_URL);
return normalizeVersion(payload.tag_name);
}
async function fetchNightlyReleaseMarker() {
const payload = await fetchReleasePayload(NIGHTLY_RELEASE_URL);
const marker = String(
payload.published_at ||
payload.created_at ||
payload.updated_at ||
payload.target_commitish ||
payload.tag_name ||
"",
).trim();
return marker;
}
function emitVersionUpdateState(state) {
_versionUpdateState = { ...state, timestamp: Date.now() };
const w = typeof window !== "undefined" ? window : null;
if (w) {
try {
w[VERSION_UPDATE_STATE_KEY] = _versionUpdateState;
} catch (e) {
console.debug?.(e);
}
try {
w.dispatchEvent(new CustomEvent(VERSION_UPDATE_EVENT, { detail: _versionUpdateState }));
} catch (e) {
console.debug?.(e);
}
}
}
export function getStoredVersionUpdateState() {
return _versionUpdateState;
}
function _buildLocalNoticeKey(localVersion, branch) {
const v = String(localVersion || "unknown")
.trim()
.toLowerCase();
const b = String(branch || "unknown")
.trim()
.toLowerCase();
return `${v}::${b}`;
}
export async function checkMajoorVersion({ force = false } = {}) {
if (typeof window === "undefined") {
return null;
}
let localVersion;
let branch;
try {
const result = await get(ENDPOINTS.VERSION);
if (!result?.ok) {
emitVersionUpdateState({ available: false });
return null;
}
localVersion = normalizeVersion(result.data?.version);
branch = String(result.data?.branch || "")
.trim()
.toLowerCase();
} catch {
emitVersionUpdateState({ available: false });
return null;
}
let skipRemoteCheck = false;
try {
if (!force) {
const lastCheck = Number(SettingsStore.get(LAST_CHECK_KEY) || 0);
if (Number.isFinite(lastCheck) && Date.now() - lastCheck < CHECK_INTERVAL_MS) {
skipRemoteCheck = true;
}
}
} catch {
// localStorage might be disabled; continue without caching
}
if (!localVersion || isNightly(localVersion, branch)) {
if (skipRemoteCheck) {
emitVersionUpdateState({ available: false });
return null;
}
let nightlyMarker;
try {
nightlyMarker = await fetchNightlyReleaseMarker();
const previousMarker = String(
SettingsStore.get(NIGHTLY_RELEASE_MARKER_KEY) || "",
).trim();
const updateAvailable = Boolean(
previousMarker && nightlyMarker && previousMarker !== nightlyMarker,
);
emitVersionUpdateState({
available: updateAvailable,
current: localVersion || "nightly",
latest: "nightly",
channel: "nightly",
});
if (updateAvailable) {
comfyToast(
{
summary: t("msg.nightlyUpdateTitle", "Majoor Assets Manager"),
detail: t(
"msg.nightlyUpdateDetail",
"A newer nightly build is available: https://github.com/MajoorWaldi/ComfyUI-Majoor-AssetsManager/releases/tag/nightly",
),
},
"info",
0,
);
}
if (nightlyMarker) {
try {
SettingsStore.set(NIGHTLY_RELEASE_MARKER_KEY, nightlyMarker);
} catch (e) {
console.debug?.(e);
}
}
} catch (error) {
console.warn("Unable to check Majoor nightly updates:", error);
emitVersionUpdateState({ available: false });
} finally {
try {
SettingsStore.set(LAST_CHECK_KEY, String(Date.now()));
} catch {
// ignore
}
}
return null;
}
if (skipRemoteCheck) {
return null;
}
let remoteVersion;
try {
remoteVersion = await fetchLatestReleaseVersion();
} catch (error) {
console.warn("Unable to check Majoor updates:", error);
emitVersionUpdateState({ available: false });
return null;
} finally {
try {
SettingsStore.set(LAST_CHECK_KEY, String(Date.now()));
} catch {
// ignore
}
}
if (!remoteVersion || !isNewerVersion(remoteVersion, localVersion)) {
emitVersionUpdateState({ available: false });
return null;
}
emitVersionUpdateState({
available: true,
current: localVersion,
latest: remoteVersion,
});
try {
const toastShownFor = String(
SettingsStore.get(VERSION_TOAST_NOTICE_VERSION_KEY) || "",
).trim();
if (toastShownFor !== remoteVersion) {
comfyToast(
{
summary: t("msg.newVersionTitle", "Majoor Assets Manager"),
detail: t(
"msg.newVersionDetail",
"A new version is available: {latest} (Current: {current})",
{
latest: remoteVersion,
current: localVersion,
},
),
},
"info",
0,
);
try {
SettingsStore.set(VERSION_TOAST_NOTICE_VERSION_KEY, remoteVersion);
} catch (e) {
console.debug?.(e);
}
}
} catch (e) {
console.debug?.(e);
}
return { current: localVersion, latest: remoteVersion };
}