bayan-api / extension /shared /analysis-controller.js
youssefreda9's picture
Phase 7.1: Cleanup & Stabilization Sprint
ca978e6
Raw
History Blame Contribute Delete
5.02 kB
/**
* Bayan — Analysis Controller (Phase 7.1 Simplified)
*
* Responsibilities (ONLY):
* - Adaptive debounce (2-tier: normal / large text)
* - Stale request cancellation
* - Text validation
* - Message passing to background.js
*
* Does NOT own:
* - Caching (background.js)
* - Retry logic (background.js)
* - Timeout (background.js)
* - Network (background.js)
*
* Requires: BAYAN (shared/constants.js)
*/
// eslint-disable-next-line no-unused-vars
const BayanController = (() => {
'use strict';
// ── Debounce config ──
const DEBOUNCE_NORMAL = 500;
const DEBOUNCE_LARGE = 1000;
const LARGE_TEXT_THRESHOLD = 2000;
// ── State ──
let _debounceTimer = null;
let _inflight = false;
// ── Protected site check ──
const _isProtected = BAYAN.PROTECTED_HOSTS.includes(window.location.hostname);
// ══════════════════════════════════════════════════════════
// Text validation
// ══════════════════════════════════════════════════════════
function hasArabic(text) {
if (!text || text.length < BAYAN.MIN_TEXT_LENGTH) return false;
const arabicChars = (text.match(/[\u0600-\u06FF]/g) || []).length;
return arabicChars >= BAYAN.MIN_ARABIC_CHARS;
}
function validateText(text) {
if (!text) return { valid: false, reason: 'empty' };
if (text.length < BAYAN.MIN_TEXT_LENGTH) return { valid: false, reason: 'too_short' };
if (text.length > BAYAN.MAX_TEXT_LENGTH) return { valid: false, reason: 'too_long' };
if (!hasArabic(text)) return { valid: false, reason: 'no_arabic' };
return { valid: true };
}
// ══════════════════════════════════════════════════════════
// Core: debounced analysis via background bridge
// ══════════════════════════════════════════════════════════
/**
* Schedule a debounced analysis.
* Background.js owns caching, retry, and timeout.
*
* @param {string} text
* @param {function(Object|null): void} onResult
* @param {function(): string} getCurrentText — staleness checker
*/
function scheduleAnalysis(text, onResult, getCurrentText) {
if (_debounceTimer) {
clearTimeout(_debounceTimer);
_debounceTimer = null;
}
if (!validateText(text).valid) {
onResult(null);
return;
}
const delay = text.length > LARGE_TEXT_THRESHOLD ? DEBOUNCE_LARGE : DEBOUNCE_NORMAL;
_debounceTimer = setTimeout(() => {
_debounceTimer = null;
// Staleness check: text changed during debounce
if (getCurrentText() !== text) return;
executeAnalysis(text, onResult, getCurrentText);
}, delay);
}
/**
* Send analysis request to background.js.
* No timeout, no retry, no cache — background handles all of that.
*/
function executeAnalysis(text, onResult, getCurrentText) {
_inflight = true;
chrome.runtime.sendMessage({ type: 'INLINE_ANALYZE', text }, (response) => {
_inflight = false;
// Extension context invalidated (e.g., extension reloaded)
if (chrome.runtime.lastError) {
console.warn('[Bayan Controller]', chrome.runtime.lastError.message);
onResult(null);
return;
}
// Staleness check: text changed during fetch
if (getCurrentText() !== text) return;
if (!response || response.error) {
onResult(null);
return;
}
onResult(response.data);
});
}
// ══════════════════════════════════════════════════════════
// Cancellation
// ══════════════════════════════════════════════════════════
function cancelAll() {
if (_debounceTimer) {
clearTimeout(_debounceTimer);
_debounceTimer = null;
}
_inflight = false;
}
function destroy() {
cancelAll();
}
// ══════════════════════════════════════════════════════════
// Public API
// ══════════════════════════════════════════════════════════
return {
scheduleAnalysis,
cancelAll,
destroy,
validateText,
hasArabic,
isProtectedSite() { return _isProtected; },
isInFlight() { return _inflight; },
};
})();