diff --git a/extension/content.js b/extension/content.js index 2f7835e..67d3674 100644 --- a/extension/content.js +++ b/extension/content.js @@ -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);