diff --git a/joPilot_extension/background.js b/joPilot_extension/background.js index 3008a7c..6efed54 100644 --- a/joPilot_extension/background.js +++ b/joPilot_extension/background.js @@ -1,10 +1,8 @@ -// background.js – Handles API communication and other background tasks for the Chrome extension - -import api from '../api/api-service'; // Import the API instance +//import api from '../api/api-service'; // Import the API instance //Utility function to log messages const log = (message, data = null) => { - console.log(`[Background.js] ${message}`, data); + console.log(`[Background.js] ${message}`, data !== null ? data : ''); }; //Listen for messages from other parts of the extension @@ -13,41 +11,99 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { switch (message.type) { - case 'testApiConnection': - handleTestApiConnection(sendResponse); - return true; //Keep the message channel open for async response + // case 'testApiConnection': + // handleTestApiConnection(sendResponse); + // return true; //Keep the message channel open for async response - case 'getUserData': - handleGetUserData(message.payload, sendResponse); - return true; + // case 'getUserData': + // handleGetUserData(message.payload, sendResponse); + // return true; case 'saveToStorage': handleSaveToStorage(message.payload, sendResponse); - return true; + return true; // Keep channel open for async response case 'fetchFromStorage': handleFetchFromStorage(message.payload, sendResponse); - return true; + return true; // Keep channel open for async response + + case 'triggerAutofill': // <<< ADD THIS CASE + handleTriggerAutofill(sendResponse); + return true; // Keep channel open for async response default: log('Unknown message type', message.type); sendResponse({ success: false, error: 'Unknown message type' }); - return false; + return false; // No async response needed } }); //Handle API connection test -const handleTestApiConnection = (sendResponse) => { - log('Testing API connection...'); - api.get('/test') - .then(response => { - log('API Response', response.data); - sendResponse({ success: true, data: response.data }); - }) - .catch(error => { - log('API Error', error); - sendResponse({ success: false, error: error.message }); +const handleFetchFromStorage = (keys, sendResponse) => { + log('Fetching data from storage', keys); + const validKeys = Array.isArray(keys) || typeof keys === 'object' || keys === null ? keys : null; + chrome.storage.local.get(validKeys, (result) => { + if (chrome.runtime.lastError) { + log('Error fetching from storage', chrome.runtime.lastError); + sendResponse({ success: false, error: chrome.runtime.lastError.message }); + } else { + log('Data fetched from storage successfully', result); + sendResponse({ success: true, data: result }); + } + }); +}; + +// Handle the autofill trigger from the popup +const handleTriggerAutofill = (sendResponse) => { + log('Autofill triggered. Fetching data...'); + chrome.storage.local.get(null, (autofillData) => { + if (chrome.runtime.lastError) { + log('Error fetching autofill data from storage', chrome.runtime.lastError); + sendResponse({ success: false, error: `Storage fetch error: ${chrome.runtime.lastError.message}` }); + return; + } + + if (Object.keys(autofillData).length === 0) { + log('No autofill data found in storage.'); + sendResponse({ success: false, error: 'No autofill data found.' }); + return; + } + + log('Autofill data fetched', autofillData); + + // Get the active tab + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs.length === 0 || !tabs[0].id) { // Check if tab exists and has an ID + log("No active tab found or tab has no ID."); + sendResponse({ success: false, error: "No active tab found." }); + return; + } + const activeTabId = tabs[0].id; + const messageToSend = { type: 'fillForm', data: autofillData }; + + // --- ADD THESE LOGS --- + console.log(`<<< BACKGROUND: Attempting to send message to Tab ID: ${activeTabId} >>>`); + console.log("<<< BACKGROUND: Message Content: >>>", messageToSend); + // --- END OF ADDED LOGS --- + + chrome.tabs.sendMessage( + activeTabId, + messageToSend, + (response) => { + if (chrome.runtime.lastError) { + console.error('<<< BACKGROUND: Error sending message to content script: >>>', chrome.runtime.lastError.message); + sendResponse({ success: false, error: `Could not communicate with content script: ${chrome.runtime.lastError.message}. Is the content script active on this page?` }); + } else if (response && response.success) { + log('<<< BACKGROUND: Content script responded with success. >>>', response); + sendResponse({ success: true }); + } else { + log('<<< BACKGROUND: Content script responded with failure or no response. >>>', response); + sendResponse({ success: false, error: response?.error || 'Content script failed or did not respond.' }); + } + } + ); }); + }); }; //Handle fetching user data from the API @@ -76,18 +132,4 @@ const handleSaveToStorage = (payload, sendResponse) => { sendResponse({ success: true }); } }); -}; - -//Handle fetching data from Chrome storage -const handleFetchFromStorage = (keys, sendResponse) => { - log('Fetching data from storage', keys); - chrome.storage.local.get(keys, (result) => { - if (chrome.runtime.lastError) { - log('Error fetching from storage', chrome.runtime.lastError); - sendResponse({ success: false, error: chrome.runtime.lastError.message }); - } else { - log('Data fetched from storage successfully', result); - sendResponse({ success: true, data: result }); - } - }); }; \ No newline at end of file diff --git a/joPilot_extension/manifest.json b/joPilot_extension/manifest.json index 3f94dec..507feff 100644 --- a/joPilot_extension/manifest.json +++ b/joPilot_extension/manifest.json @@ -1,20 +1,28 @@ { - "manifest_version": 2, - "name": "JoPilot", - "version": "1.0", - "description": "Companion for Job Applications", - "icons": { - "16": "public/favicon.png" - }, - "browser_action": { - "default_popup": "popup/popup.html" - }, - "background": { - "scripts": ["background.js"], - "persistent": false - }, - "permissions": [ - "storage", - "activeTab" - ] + "manifest_version": 2, + "name": "JoPilot", + "version": "1.0", + "description": "Companion for Job Applications", + "icons": { + "16": "public/favicon.png" + }, + "browser_action": { + "default_popup": "popup/popup.html" + }, + "background": { + "scripts": ["background.js"], + "persistent": false + }, + "permissions": [ + "storage", + "activeTab" + ], + + "content_scripts": [ + { + "matches": [""], + "js": ["public/contentScript.js"], + "run_at": "document_idle" + } + ] } \ No newline at end of file diff --git a/joPilot_extension/popup/popup.js b/joPilot_extension/popup/popup.js index 5ff3af0..8297d5b 100644 --- a/joPilot_extension/popup/popup.js +++ b/joPilot_extension/popup/popup.js @@ -1,32 +1,87 @@ -// popup.js – Handles button interactions and communicates with background.js -//primarily used for scripts for popup.html which is the body of the extension + // Utility function to display output in the popup const displayOutput = (message) => { const outputDiv = document.getElementById('output'); - outputDiv.textContent = message; + + if (outputDiv) { + outputDiv.textContent = message; + } else { + console.error("Output div not found in popup.html"); + } }; // Add event listener for sync button -document.getElementById('syncButton').addEventListener('click', () => { - chrome.runtime.sendMessage({ type: 'syncData' }, (response) => { - if (response.success) { - displayOutput(`Data synced: ${JSON.stringify(response.data)}`); - } else { - displayOutput(`Error: ${response.error}`); - } +const syncButton = document.getElementById('syncButton'); +if (syncButton) { + syncButton.addEventListener('click', () => { + chrome.runtime.sendMessage({ type: 'syncData' }, (response) => { + if (chrome.runtime.lastError) { + displayOutput(`Error: ${chrome.runtime.lastError.message}`); + return; + } + if (response) { + if (response.success) { + displayOutput(`Data synced: ${JSON.stringify(response.data || {})}`); + } else { + displayOutput(`Error: ${response.error || 'Unknown error during sync'}`); + } + } else { + displayOutput('No response received from background script during sync.'); + } + }); }); -}); +} else { + console.error("Sync button not found in popup.html"); +} // Add event listener for autofill data -document.getElementById('autofillButton').addEventListener('click', () => { - chrome.runtime.sendMessage({ type: 'autofilledData' }, (response) => { - if (response.success) { - displayOutput('Information filled successfully!'); - } else { - displayOutput(`Error: ${response.error}`); - } +const autofillButton = document.getElementById('autofillButton'); +if (autofillButton) { + autofillButton.addEventListener('click', () => { + + console.log("<<< POPUP: Autofill button clicked! Attempting to send 'triggerAutofill' message... >>>"); + + chrome.runtime.sendMessage({ type: 'triggerAutofill' }, (response) => { + + if (chrome.runtime.lastError) { + console.error("<<< POPUP: Error received after sending message: >>>", chrome.runtime.lastError.message); + displayOutput(`Error: ${chrome.runtime.lastError.message}`); + return; + } + if (response) { + console.log("<<< POPUP: Response received from background: >>>", response); + if (response.success) { + displayOutput('Autofill requested successfully!'); + } else { + displayOutput(`Error: ${response.error || 'Unknown error during autofill trigger'}`); + } + } else { + console.log("<<< POPUP: No response received from background script. >>>"); + displayOutput('No response received from background script for autofill trigger.'); + } + }); + + }); -}); +} else { + console.error("Autofill button not found in popup.html"); +} +// Create an output div if it doesn't exist, for debugging purposes +if (!document.getElementById('output')) { + const outputDiv = document.createElement('div'); + outputDiv.id = 'output'; + outputDiv.style.marginTop = '10px'; + outputDiv.style.padding = '5px'; + outputDiv.style.border = '1px solid #ccc'; + outputDiv.style.minHeight = '20px'; + outputDiv.style.wordWrap = 'break-word'; + const buttonContainer = document.getElementById('buttonContainer'); + if (buttonContainer) { + buttonContainer.parentNode.insertBefore(outputDiv, buttonContainer.nextSibling); + } else { + document.body.appendChild(outputDiv); // Fallback append + } +} diff --git a/joPilot_extension/public/contentScript.js b/joPilot_extension/public/contentScript.js new file mode 100644 index 0000000..ab24477 --- /dev/null +++ b/joPilot_extension/public/contentScript.js @@ -0,0 +1,168 @@ +console.log("JoPilot Content Script Loaded!"); + + +function highlightInputFields() { + const fields = document.querySelectorAll( + 'input[type="text"], input[type="email"], input[type="tel"], input[type="date"], textarea' + ); + console.log(`Found ${fields.length} input fields/textareas to highlight.`); + fields.forEach(field => { + field.style.border = '2px solid yellow'; + field.style.backgroundColor = '#FFFFE0'; + }); +} +highlightInputFields(); // Run highlighting on load + + +// Helper function to standardize labels +function simplifyLabel(text) { + if (!text) return ''; + return text.replace(/[^a-z0-9]/gi, '').toLowerCase(); +} + +// Function to find the label associated with a field element +function findLabelForField(fieldElement) { + let labelText = ""; + const fieldId = fieldElement.getAttribute('id'); + + if (fieldId) { + const labels = document.querySelectorAll(`label[for="${fieldId}"]`); + if (labels.length > 0) { + labelText = labels[0].textContent.trim(); + if (labelText) return labelText; + } + } + + let parent = fieldElement.parentElement; + if (parent) { + const labelsInParent = parent.querySelectorAll('label'); + if (labelsInParent.length === 1) { // Only if there's a single unambiguous label + labelText = labelsInParent[0].textContent.trim(); + if (labelText) return labelText; + } + } + + + let sibling = fieldElement.previousElementSibling; + while(sibling) { + if (sibling.tagName === 'LABEL') { + labelText = sibling.textContent.trim(); + if (labelText) return labelText; + break; // Stop searching once a label is found, even if empty + } + sibling = sibling.previousElementSibling; + } + + + const placeholder = fieldElement.getAttribute('placeholder'); + if (placeholder) { + return placeholder.trim(); + } + + return ""; +} + + +// Function to fill form fields +function fillFormFields(autofillData) { + console.log("Attempting to fill form with data:", autofillData); + let fieldsFilled = 0; + + const fields = document.querySelectorAll( + 'input[type="text"], input[type="email"], input[type="tel"], input[type="date"], textarea' + ); + + fields.forEach(field => { + const labelText = findLabelForField(field); + if (!labelText) { + console.log("Skipping field with no identifiable label:", field); + return; + } + + const simplifiedLabel = simplifyLabel(labelText); + let filled = false; + + + for (const key in autofillData) { + const simplifiedKey = simplifyLabel(key); + + + if (simplifiedKey && simplifiedLabel && + (simplifiedKey.includes(simplifiedLabel) || simplifiedLabel.includes(simplifiedKey))) { + + console.log(`Match found: Label "${labelText}" (Simplified: ${simplifiedLabel}) matches Key "${key}" (Simplified: ${simplifiedKey}). Filling with "${autofillData[key]}"`); + + field.value = autofillData[key]; + + field.dispatchEvent(new Event('input', { bubbles: true })); + field.dispatchEvent(new Event('change', { bubbles: true })); + + fieldsFilled++; + filled = true; + break; + } + } + if (!filled) { + console.log(`No matching key found for label: "${labelText}" (Simplified: ${simplifiedLabel})`); + } + }); + + console.log(`Autofill attempt complete. Filled ${fieldsFilled} fields.`); + return fieldsFilled > 0; +} + +// Listen for messages from the background script +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + console.log("Content script received message:", message); + + if (message.type === 'fillForm') { + if (!message.data || Object.keys(message.data).length === 0) { + console.error("Received fillForm message but no data was provided."); + sendResponse({ success: false, error: "No data provided for autofill." }); + return; + } + try { + const success = fillFormFields(message.data); + if (success) { + sendResponse({ success: true }); + } else { + sendResponse({ success: false, error: "No matching fields found to fill." }); + } + } catch (error) { + console.error("Error during form filling:", error); + sendResponse({ success: false, error: `Error during fill: ${error.message}` }); + } + } + +}); + +// Listen for messages from the background script +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + console.log("<<< CONTENT SCRIPT: Message received inside listener! >>>", message); + + if (message.type === 'fillForm') { + console.log("<<< CONTENT SCRIPT: Received 'fillForm' message type. >>>"); + if (!message.data || Object.keys(message.data).length === 0) { + console.error("<<< CONTENT SCRIPT: No data provided for autofill. >>>"); + sendResponse({success: false, error: "No data provided for autofill."}); + return; // Stop processing if no data + } + try { + const success = fillFormFields(message.data); + if (success) { + console.log("<<< CONTENT SCRIPT: Form filling successful. Sending response. >>>"); + sendResponse({success: true}); + } else { + console.log("<<< CONTENT SCRIPT: No fields matched. Sending response. >>>"); + sendResponse({success: false, error: "No matching fields found to fill."}); + } + } catch (error) { + console.error("<<< CONTENT SCRIPT: Error during form filling:", error, ">>>"); + sendResponse({success: false, error: `Error during fill: ${error.message}`}); + } + } else { + console.log("<<< CONTENT SCRIPT: Received message, but type was not 'fillForm'. Type was: ", message.type, ">>>"); + } +}); + +console.log("<<< CONTENT SCRIPT: Message listener attached. >>>"); \ No newline at end of file