Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/env_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 =<your-extension-id>

# -----------------------------
# Database Settings
# -----------------------------
Expand Down
64 changes: 64 additions & 0 deletions docs/setup/chrome_extension_setup.md
Original file line number Diff line number Diff line change
@@ -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"}`.
96 changes: 96 additions & 0 deletions extension/background.js
Original file line number Diff line number Diff line change
@@ -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: '.' }] });
24 changes: 24 additions & 0 deletions extension/manifest.json
Original file line number Diff line number Diff line change
@@ -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": [
"<all_urls>"
],
"externally_connectable": {
"matches": [
"*://localhost/*",
"*://your-devsync-domain.com/*"
]
}
}
Loading