Sync AI limit status across tabs
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user