Sync AI limit status across tabs

This commit is contained in:
2026-04-07 16:16:09 +02:00
parent d600b2a3b6
commit 2fea16f6de

View File

@@ -1017,11 +1017,14 @@ const aiCredentialCache = {
pending: null
};
const AI_SETTINGS_CACHE_TTL = 30 * 1000;
const AI_SETTINGS_SYNC_KEY = 'fb_tracker_ai_settings_sync';
const aiSettingsCache = {
data: null,
timestamp: 0,
pending: null
};
const aiAvailabilitySubscribers = new Set();
let aiSettingsBroadcastChannel = null;
const MODERATION_SETTINGS_CACHE_TTL = 5 * 60 * 1000;
const moderationSettingsCache = {
data: null,
@@ -1141,6 +1144,97 @@ function backendFetch(url, options = {}) {
return fetch(url, config);
}
function notifyAIAvailabilitySubscribers() {
aiAvailabilitySubscribers.forEach((subscriber) => {
try {
subscriber(aiSettingsCache.data || null);
} catch (error) {
console.warn('[FB Tracker] Failed to notify AI availability subscriber:', error);
}
});
}
function ensureAISettingsBroadcastChannel() {
if (aiSettingsBroadcastChannel || typeof BroadcastChannel !== 'function') {
return aiSettingsBroadcastChannel;
}
try {
aiSettingsBroadcastChannel = new BroadcastChannel('fb-tracker-ai-settings');
aiSettingsBroadcastChannel.addEventListener('message', (event) => {
const data = event && event.data ? event.data : null;
if (!data || !data.settings) {
return;
}
if (data.timestamp && data.timestamp <= aiSettingsCache.timestamp) {
return;
}
applyAISettingsSnapshot(data.settings, { timestamp: data.timestamp || Date.now(), broadcast: false });
});
} catch (error) {
console.warn('[FB Tracker] Failed to initialize AI settings BroadcastChannel:', error);
aiSettingsBroadcastChannel = null;
}
return aiSettingsBroadcastChannel;
}
function applyAISettingsSnapshot(settings, options = {}) {
if (!settings || typeof settings !== 'object') {
return;
}
const { timestamp = Date.now(), broadcast = false } = options;
aiSettingsCache.data = settings;
aiSettingsCache.timestamp = timestamp;
notifyAIAvailabilitySubscribers();
if (!broadcast) {
return;
}
const payload = JSON.stringify({
timestamp,
settings
});
try {
localStorage.setItem(AI_SETTINGS_SYNC_KEY, payload);
} catch (error) {
console.warn('[FB Tracker] Failed to sync AI settings via localStorage:', error);
}
try {
ensureAISettingsBroadcastChannel()?.postMessage({ timestamp, settings });
} catch (error) {
console.warn('[FB Tracker] Failed to sync AI settings via BroadcastChannel:', error);
}
}
window.addEventListener('storage', (event) => {
if (!event || event.key !== AI_SETTINGS_SYNC_KEY || !event.newValue) {
return;
}
try {
const payload = JSON.parse(event.newValue);
if (!payload || !payload.settings) {
return;
}
if (payload.timestamp && payload.timestamp <= aiSettingsCache.timestamp) {
return;
}
applyAISettingsSnapshot(payload.settings, {
timestamp: payload.timestamp || Date.now(),
broadcast: false
});
} catch (error) {
console.warn('[FB Tracker] Failed to process synced AI settings:', error);
}
});
ensureAISettingsBroadcastChannel();
async function fetchActiveAICredentials(forceRefresh = false) {
const now = Date.now();
if (!forceRefresh && aiCredentialCache.data && (now - aiCredentialCache.timestamp < AI_CREDENTIAL_CACHE_TTL)) {
@@ -1205,8 +1299,7 @@ async function fetchAISettings(forceRefresh = false) {
}
const settings = await response.json();
aiSettingsCache.data = settings;
aiSettingsCache.timestamp = Date.now();
applyAISettingsSnapshot(settings, { timestamp: Date.now(), broadcast: forceRefresh });
return settings;
})();
@@ -1226,6 +1319,26 @@ function getAIAutoCommentRateLimitStatusFromSettings(settings, profileNumber) {
return statuses.find((entry) => parseInt(entry?.profile_number, 10) === normalizedProfile) || null;
}
function upsertAIAutoCommentRateLimitStatus(settings, status) {
if (!status || typeof status !== 'object') {
return settings;
}
const existing = settings && typeof settings === 'object'
? settings
: { enabled: 1, rate_limit_statuses: [] };
const currentStatuses = Array.isArray(existing.rate_limit_statuses) ? existing.rate_limit_statuses : [];
const normalizedProfile = parseInt(status.profile_number, 10);
const nextStatuses = currentStatuses.filter((entry) => parseInt(entry?.profile_number, 10) !== normalizedProfile);
nextStatuses.push(status);
nextStatuses.sort((left, right) => (parseInt(left?.profile_number, 10) || 0) - (parseInt(right?.profile_number, 10) || 0));
return {
...existing,
rate_limit_statuses: nextStatuses
};
}
function formatAIAutoCommentRateLimitReason(status) {
if (!status || !status.blocked_reason) {
return 'Aktion aktuell gesperrt';
@@ -6807,6 +6920,27 @@ async function addAICommentButton(container, postElement) {
return await rateLimitRefreshPromise;
};
const handleSharedAISettingsUpdate = async (settings) => {
if (!wrapper.isConnected) {
aiAvailabilitySubscribers.delete(handleSharedAISettingsUpdate);
return;
}
try {
const profileNumber = await fetchBackendProfileNumber();
if (!profileNumber) {
applyAvailabilityState(null);
return;
}
const status = getAIAutoCommentRateLimitStatusFromSettings(settings, profileNumber);
applyAvailabilityState(status);
} catch (error) {
console.warn('[FB Tracker] Failed to apply shared AI settings update:', error);
}
};
aiAvailabilitySubscribers.add(handleSharedAISettingsUpdate);
const updateNoteIndicator = () => {
const note = getAdditionalNote();
const hasNote = note.trim().length > 0;
@@ -7577,9 +7711,11 @@ async function addAICommentButton(container, postElement) {
});
mergeTraceInfo(aiResult);
if (aiResult.autoCommentRateLimitStatus) {
const nextSettings = upsertAIAutoCommentRateLimitStatus(aiSettingsCache.data, aiResult.autoCommentRateLimitStatus);
applyAISettingsSnapshot(nextSettings, { timestamp: Date.now(), broadcast: true });
applyAvailabilityState(aiResult.autoCommentRateLimitStatus);
} else {
void refreshAvailabilityState(true);
void refreshAvailabilityState(true, profileNumber);
}
throwIfCancelled();
@@ -7694,7 +7830,11 @@ async function addAICommentButton(container, postElement) {
}
if (error && error.status === 429) {
await refreshAvailabilityState(true);
const syncedStatus = await refreshAvailabilityState(true);
if (syncedStatus) {
const nextSettings = upsertAIAutoCommentRateLimitStatus(aiSettingsCache.data, syncedStatus);
applyAISettingsSnapshot(nextSettings, { timestamp: Date.now(), broadcast: true });
}
}
const cancelled = aiContext.cancelled || isCancellationError(error);