diff --git a/docs/env_guide.md b/docs/env_guide.md index 32a0fe9..46562a8 100644 --- a/docs/env_guide.md +++ b/docs/env_guide.md @@ -17,6 +17,9 @@ PORT=5000 # URL of the frontend client CLIENT_URL=http://localhost:5173 +# ID of the chrome extension, if should be in frontend .env +VITE_CHROME_EXTENSION_ID = + # ----------------------------- # Database Settings # ----------------------------- diff --git a/docs/setup/chrome_extension_setup.md b/docs/setup/chrome_extension_setup.md new file mode 100644 index 0000000..9403860 --- /dev/null +++ b/docs/setup/chrome_extension_setup.md @@ -0,0 +1,64 @@ +# DevSync Companion Extension Setup Guide + +This guide details how to set up the Chrome extension that acts as a "Focus Mode" companion for the main DevSync frontend application. + +## Project Structure + +This project is a monorepo containing three main parts: + +The `frontend` app (running on `localhost`) sends messages to the `extension` to tell it when to start or stop blocking distracting websites. This setup guide explains how to establish that connection for local development. + +## Part 1: Loading the Extension in Your Browser + +1. **Open Your Browser:** This guide assumes you are using Google Chrome, Brave, or another Chromium-based browser. + +2. **Navigate to Extensions:** Open a new tab and go to the following URL: + `chrome://extensions` + +3. **Enable Developer Mode:** In the top-right corner of the page, find the **Developer mode** toggle and switch it **ON**. This will reveal a new menu with "Load unpacked". + +4. **Load the Extension:** + * Click the **Load unpacked** button. + * A file dialog will open. Navigate to your project's root folder and select the **`extension`** folder. + * Click "Select Folder". + +5. **Verify Installation:** If successful, you will instantly see a new card on the page for "DevSync Enforcer" (or whatever name is in your `manifest.json`). + +## Part 2: Connecting the Frontend to the Extension + +For security, your `frontend` React app is not allowed to talk to any extension *unless* it knows its unique, randomly-generated ID. You must provide this ID to your React app. + +1. **Find Your Extension ID:** + * On the `chrome://extensions` page, find the "DevSync Enforcer" card. + * Look for the **`ID`** field (it's a long string of random letters). + * **Copy this ID** to your clipboard. + +2. **Set the Environment Variable:** + * Navigate to your **`/frontend`** folder. + * Create a new file named `.env` in this folder (if it doesn't already exist). + * Inside the `.env` file, add the ID you just copied, prefixed with `VITE_`: + + ```.env + VITE_CHROME_EXTENSION_ID="YOUR_COPIED_ID_GOES_HERE" + ``` + *(Example: `VITE_CHROME_EXTENSION_ID="faanknakkahoapilfgibbekefgbodfkk"`)* + +3. **Restart Your Frontend Server:** + * This step is **critical**. Vite only loads `.env` variables on startup. + * If your server is running, **stop it** (Ctrl+C in the terminal). + * **Restart** your server (e.g., `npm run dev`). + +Your `frontend` app can now securely send messages to your local `extension`. + +## Part 3: Testing and Debugging + +Your setup is now complete. To test it: + +1. Open your `frontend` app (e.g., `http://localhost:5173/pomodoro`). +2. Open a few other tabs (like `google.com` or any site not on your allow-list). +3. In your `frontend` app, open the Developer Console (F12 or Ctrl+Shift+I). +4. Start the **Work** timer. + +You should see two things happen: +1. The other tabs (like `google.com`) should automatically close. +2. In your Developer Console, you should see a log like `Extension acknowledged start. {status: "Focus started"}`. diff --git a/extension/background.js b/extension/background.js new file mode 100644 index 0000000..072fbac --- /dev/null +++ b/extension/background.js @@ -0,0 +1,96 @@ +const ALLOWED_DOMAINS = [ + 'localhost', + 'your-devsync-domain.com', + 'stackoverflow.com', + 'developer.mozilla.org', + + // --- Social Profiles --- + 'github.com', + 'gitlab.com', + 'linkedin.com', + + // --- Competitive Coding --- + 'leetcode.com', + 'codechef.com', + 'hackerrank.com', + 'codeforces.com', + 'hackerearth.com' + + // Add any other sites you need (e.G., 'vercel.app', 'aws.amazon.com') +]; + + +chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => { + if (request.command === "startFocus") { + console.log(`Focus session starting for ${request.duration} minutes.`); + chrome.storage.local.set({ isFocusActive: true }); + closeUnwantedTabs(); + if (request.duration && request.duration > 0) { + chrome.alarms.create("stopFocus", { delayInMinutes: request.duration }); + } + + sendResponse({ status: "Focus started" }); + + } else if (request.command === "stopFocus") { + console.log("Focus session stopping by user request."); + stopFocusSession(); + sendResponse({ status: "Focus stopped" }); + } + return true; +}); + +chrome.alarms.onAlarm.addListener((alarm) => { + if (alarm.name === "stopFocus") { + console.log("Alarm fired: Focus session stopping."); + stopFocusSession(); + } +}); + +function stopFocusSession() { + chrome.storage.local.set({ isFocusActive: false }); + chrome.alarms.clear("stopFocus"); + console.log("Focus session deactivated. All sites unblocked."); +} + +function closeUnwantedTabs() { + chrome.tabs.query({}, (tabs) => { + for (let tab of tabs) { + if (!tab.url) continue; + + try { + const url = new URL(tab.url); + const domain = url.hostname.replace('www.', ''); + const isAllowed = ALLOWED_DOMAINS.some(allowedDomain => domain.endsWith(allowedDomain)); + + if (!isAllowed) { + if (!tab.pinned && !tab.url.startsWith('chrome://')) { + chrome.tabs.remove(tab.id); + } + } + } catch (e) { + console.warn(`Could not parse URL, skipping: ${tab.url}`, e); + } + } + }); +} + +chrome.webNavigation.onBeforeNavigate.addListener((details) => { + + if (details.frameId !== 0) { + return; + } + + chrome.storage.local.get("isFocusActive", (data) => { + if (data.isFocusActive) { + const url = new URL(details.url); + const domain = url.hostname.replace('www.', ''); + + const isAllowed = ALLOWED_DOMAINS.some(allowedDomain => domain.endsWith(allowedDomain)); + + if (!isAllowed && !details.url.startsWith('chrome://')) { + console.log(`Blocking navigation to: ${details.url}`); + chrome.tabs.remove(details.tabId); + } + } + }); +}, { url: [{ hostContains: '.' }] }); \ No newline at end of file diff --git a/extension/manifest.json b/extension/manifest.json new file mode 100644 index 0000000..0461473 --- /dev/null +++ b/extension/manifest.json @@ -0,0 +1,24 @@ +{ + "manifest_version": 3, + "name": "DevSync Enforcer", + "version": "1.0", + "description": "Companion extension for the DevSync Pomodoro app.", + "permissions": [ + "tabs", + "storage", + "webNavigation", + "alarms" + ], + "background": { + "service_worker": "background.js" + }, + "host_permissions": [ + "" + ], + "externally_connectable": { + "matches": [ + "*://localhost/*", + "*://your-devsync-domain.com/*" + ] + } +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index dc642fd..ef7e4e3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -530,6 +530,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -546,6 +547,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -562,6 +564,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -578,6 +581,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -594,6 +598,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -610,6 +615,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -626,6 +632,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -642,6 +649,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -658,6 +666,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -674,6 +683,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -690,6 +700,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -706,6 +717,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -722,6 +734,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -738,6 +751,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -754,6 +768,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -770,6 +785,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -786,6 +802,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -802,6 +819,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -818,6 +836,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -834,6 +853,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -850,6 +870,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -866,6 +887,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -882,6 +904,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -898,6 +921,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -914,6 +938,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -930,6 +955,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3099,6 +3125,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3112,6 +3139,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3125,6 +3153,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3138,6 +3167,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3151,6 +3181,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3164,6 +3195,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3177,6 +3209,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3190,6 +3223,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3203,6 +3237,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3216,6 +3251,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3229,6 +3265,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3242,6 +3279,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3255,6 +3293,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3268,6 +3307,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3281,6 +3321,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3294,6 +3335,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3307,6 +3349,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3320,6 +3363,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3333,6 +3377,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3346,6 +3391,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3855,6 +3901,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -3868,7 +3915,7 @@ "version": "24.1.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -3878,7 +3925,7 @@ "version": "19.1.9", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz", "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -3888,7 +3935,7 @@ "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", - "devOptional": true, + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.0.0" @@ -4965,6 +5012,7 @@ "version": "0.25.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -5646,6 +5694,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7768,6 +7817,7 @@ "version": "4.46.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.1.tgz", "integrity": "sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -8314,6 +8364,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -8330,6 +8381,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -8347,6 +8399,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -8483,7 +8536,7 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/universalify": { @@ -8676,6 +8729,7 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -8750,6 +8804,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -8767,6 +8822,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" diff --git a/frontend/src/Components/DashBoard/Pomodoro.jsx b/frontend/src/Components/DashBoard/Pomodoro.jsx index 59bf988..71e91f4 100644 --- a/frontend/src/Components/DashBoard/Pomodoro.jsx +++ b/frontend/src/Components/DashBoard/Pomodoro.jsx @@ -1,11 +1,10 @@ -// src/Components/DashBoard/Pomodoro.jsx import React from 'react'; -import { useTimer } from "../../context/TimerContext"; +import { useTimer } from "../../context/TimerContext"; import { useNavigate } from 'react-router-dom'; const Pomodoro = () => { const navigate = useNavigate(); - const { timeLeft, isWork, startTimer, pauseTimer, resetTimer } = useTimer(); + const { timeLeft, isWork, startTimer, pauseTimer, resetTimer, isRunning } = useTimer(); const formatTime = (seconds) => { const mins = String(Math.floor(seconds / 60)).padStart(2, '0'); @@ -40,18 +39,21 @@ const Pomodoro = () => {
- - + {!isRunning ? ( + + ) : ( + + )}