diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 091aef9..9d010cb 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -62,6 +62,6 @@ KoalaSync uses a megaphone routing approach to minimize server logic: ## 8. Constant Synchronization & Consistency To maintain a "Single Source of Truth" across the server and extension without using a bundler: - **Relay Server & Extension Modules**: `background.js` and `popup.js` import constants directly from `shared/constants.js`. -- **Content Scripts**: To ensure zero-latency execution, `content.js` uses a manual mirror of `EVENTS`. -- **Synchronization**: The `node scripts/build-extension.js` script ensures that the `shared/` folder within the `extension/` directory is kept up-to-date with the root `shared/` source while packaging artifacts. -- **Verification**: Any protocol change requires a manual verification sweep across all three constant locations (Shared, Server, and Content Script Mirror). +- **Content Scripts**: To ensure zero-latency execution, `content.js` uses a synchronized copy of `EVENTS` and constants. +- **Automation**: The `node scripts/build-extension.js` script automatically injects these constants into `content.js` during the build process, eliminating the risk of manual mirror mismatch. +- **Verification**: Any protocol change is automatically propagated across the stack by running the build script. diff --git a/extension/content.js b/extension/content.js index 912077d..2dc039e 100644 --- a/extension/content.js +++ b/extension/content.js @@ -506,6 +506,10 @@ }); observer.observe(document.body, { childList: true, subtree: true }); + // --- SHARED_HEARTBEAT_INJECT_START --- + const HEARTBEAT_INTERVAL_VAL = 15000; + // --- SHARED_HEARTBEAT_INJECT_END --- + // Heartbeat let heartbeatErrorCount = 0; const heartbeatInterval = setInterval(() => { @@ -532,7 +536,7 @@ } }); } - }, 15000); + }, HEARTBEAT_INTERVAL_VAL); // Initial Setup setupListeners(); diff --git a/scripts/build-extension.js b/scripts/build-extension.js index 142eb3e..96e68c7 100644 --- a/scripts/build-extension.js +++ b/scripts/build-extension.js @@ -45,12 +45,19 @@ function copyExtensionFiles(targetDir) { const masterConstantsPath = path.join(rootDir, 'shared', 'constants.js'); const constantsContent = fs.readFileSync(masterConstantsPath, 'utf8'); - // Extract the EVENTS object using regex - const eventsMatch = constantsContent.match(/export const EVENTS = ({[\s\S]+?});/); + // Robust Extraction using flexible regex + const eventsMatch = constantsContent.match(/export const EVENTS\s*=\s*({[\s\S]+?});/); + const heartbeatMatch = constantsContent.match(/export const HEARTBEAT_INTERVAL\s*=\s*(\d+);/); + if (!eventsMatch) { throw new Error('CRITICAL: Could not find EVENTS object in shared/constants.js'); } + if (!heartbeatMatch) { + throw new Error('CRITICAL: Could not find HEARTBEAT_INTERVAL in shared/constants.js'); + } + const eventsObject = eventsMatch[1]; + const heartbeatVal = heartbeatMatch[1]; const items = fs.readdirSync(extDir); for (const item of items) { @@ -65,20 +72,33 @@ function copyExtensionFiles(targetDir) { if (item === 'content.js') { // Perform injection let content = fs.readFileSync(srcPath, 'utf8'); - const startMarker = '// --- SHARED_EVENTS_INJECT_START ---'; - const endMarker = '// --- SHARED_EVENTS_INJECT_END ---'; - const pattern = new RegExp(`${startMarker}[\\s\\S]+?${endMarker}`); - const replacement = `${startMarker}\n // This block is automatically updated by /scripts/build-extension.js\n const EVENTS = ${eventsObject};\n ${endMarker}`; + // 1. Inject Events + const eStart = '// --- SHARED_EVENTS_INJECT_START ---'; + const eEnd = '// --- SHARED_EVENTS_INJECT_END ---'; + const ePattern = new RegExp(`${eStart}[\\s\\S]+?${eEnd}`); + const eRep = `${eStart}\n // This block is automatically updated by /scripts/build-extension.js\n const EVENTS = ${eventsObject};\n ${eEnd}`; - if (pattern.test(content)) { - content = content.replace(pattern, replacement); - fs.writeFileSync(destPath, content); - console.log('✓ Injected shared events into content.js'); + if (ePattern.test(content)) { + content = content.replace(ePattern, eRep); } else { - console.warn('⚠️ WARNING: Markers not found in content.js, skipping injection.'); - fs.copyFileSync(srcPath, destPath); + console.warn('⚠️ WARNING: Event markers not found in content.js'); } + + // 2. Inject Heartbeat + const hStart = '// --- SHARED_HEARTBEAT_INJECT_START ---'; + const hEnd = '// --- SHARED_HEARTBEAT_INJECT_END ---'; + const hPattern = new RegExp(`${hStart}[\\s\\S]+?${hEnd}`); + const hRep = `${hStart}\n const HEARTBEAT_INTERVAL_VAL = ${heartbeatVal};\n ${hEnd}`; + + if (hPattern.test(content)) { + content = content.replace(hPattern, hRep); + } else { + console.warn('⚠️ WARNING: Heartbeat markers not found in content.js'); + } + + fs.writeFileSync(destPath, content); + console.log('✓ Injected shared constants into content.js'); } else { fs.copyFileSync(srcPath, destPath); }