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
Binary file added extension/alarm.mp3
Binary file not shown.
229 changes: 187 additions & 42 deletions extension/background.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,213 @@
const ALLOWED_DOMAINS = [
'localhost',
'your-devsync-domain.com',
'localhost',
'devsync-one.vercel.app',
'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')
];

let creating;
async function setupOffscreenDocument(path) {
if (await chrome.offscreen.hasDocument()) return;
if (creating) {
await creating;
} else {
creating = chrome.offscreen.createDocument({
url: path,
reasons: ['AUDIO_PLAYBACK'],
justification: 'To play alarm sound for Pomodoro timer',
});
await creating;
creating = null;
}
}

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 });
async function playSound() {
await setupOffscreenDocument('offscreen.html');
await chrome.runtime.sendMessage({ action: "playSound" });
}

chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === "devsync-tick") {

const data = await chrome.storage.local.get([
'timerEndTime', 'timerSessionType', 'timerSessionCount',
'workTime', 'shortBreak', 'longBreak', 'SESSIONS_BEFORE_LONG_BREAK'
]);

if (!data.timerEndTime) {
stopTimerSession(false);
return;
}

sendResponse({ status: "Focus started" });
const now = Date.now();
const remaining = data.timerEndTime - now;
const timeLeftInSeconds = Math.ceil(remaining / 1000);

if (timeLeftInSeconds > 0) {
broadcastToAllTabs({
action: "updateTime",
timeLeft: timeLeftInSeconds,
});
chrome.alarms.create("devsync-tick", { delayInMinutes: 1/60 });
} else {
handleSessionEnd(data);
}
}
});


function handleSessionEnd(data) {
playSound();

let nextSessionType;
let nextTime;
let nextSessionCount = data.timerSessionCount;

} else if (request.command === "stopFocus") {
console.log("Focus session stopping by user request.");
stopFocusSession();
sendResponse({ status: "Focus stopped" });
if (data.timerSessionType === 'work') {
nextSessionCount = data.timerSessionCount + 1;
if (nextSessionCount % data.SESSIONS_BEFORE_LONG_BREAK === 0) {
nextSessionType = 'longBreak';
nextTime = data.longBreak;
} else {
nextSessionType = 'shortBreak';
nextTime = data.shortBreak;
}
} else {
if (data.timerSessionType === 'longBreak') {
chrome.notifications.create({
type: 'basic',
iconUrl: 'icon128.png',
title: 'Cycle Complete!',
message: 'Great work! You finished all 4 sessions. Time for a well-deserved break.'
});

stopTimerSession(true);
return;
} else {
nextSessionType = 'work';
nextTime = data.workTime;
}
}

const newEndTime = Date.now() + nextTime * 1000;
startTimer(newEndTime, nextSessionType, nextSessionCount, data.workTime, data.shortBreak, data.longBreak, data.SESSIONS_BEFORE_LONG_BREAK);
}

function startTimer(endTime, sessionType, sessionCount, workTime, shortBreak, longBreak, sessionsBeforeLongBreak) {
const isWorkSession = sessionType === 'work';

chrome.storage.local.set({
isRunning: true,
isFocusActive: isWorkSession,
timerEndTime: endTime,
timerSessionType: sessionType,
timerSessionCount: sessionCount,
workTime: workTime,
shortBreak: shortBreak,
longBreak: longBreak,
SESSIONS_BEFORE_LONG_BREAK: sessionsBeforeLongBreak
}, () => {

if (isWorkSession) {
closeUnwantedTabs();
}

broadcastToAllTabs({
action: "startTimer",
endTime: endTime,
sessionType: sessionType,
sessionCount: sessionCount
});

chrome.alarms.create("devsync-tick", { delayInMinutes: 1/60 });
});
}

chrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => {
if (request.command === "startTimer") {
console.log("BG: Received startTimer from React");
startTimer(
request.endTime,
request.sessionType,
request.sessionCount,
request.settings.workTime,
request.settings.shortBreak,
request.settings.longBreak,
request.settings.SESSIONS_BEFORE_LONG_BREAK
);
sendResponse({ status: "Timer started" });

} else if (request.command === "stopTimer") {
console.log("BG: Received stopTimer from React");
stopTimerSession(true);
sendResponse({ status: "Timer stopped" });

} else if (request.command === "getState") {
console.log("BG: Received getState from React");
chrome.storage.local.get([
'isRunning', 'timerEndTime', 'timerSessionType', 'timerSessionCount'
], (data) => {
sendResponse(data);
});
return true;
}
return true;
});

chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === "stopFocus") {
console.log("Alarm fired: Focus session stopping.");
stopFocusSession();
function stopTimerSession(broadcastUpdate) {
chrome.alarms.clear("devsync-tick");

chrome.storage.local.set({
isRunning: false,
isFocusActive: false,
timerEndTime: null,
timerSessionType: 'work',
timerSessionCount: 0
});

if (broadcastUpdate) {
broadcastToAllTabs({ action: "stopTimer" });
}
});

console.log("Timer session deactivated. All sites unblocked.");
}

function stopFocusSession() {
chrome.storage.local.set({ isFocusActive: false });
chrome.alarms.clear("stopFocus");
console.log("Focus session deactivated. All sites unblocked.");
function broadcastToAllTabs(message) {
chrome.tabs.query({}, (tabs) => {
for (let tab of tabs) {
chrome.tabs.sendMessage(tab.id, message, (response) => {
if (chrome.runtime.lastError) { }
});
}
});
}

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));

const domain = url.hostname;
const isAllowed = ALLOWED_DOMAINS.some(allowedDomain =>
domain === allowedDomain || domain.endsWith('.' + allowedDomain)
);
if (!isAllowed) {
if (!tab.pinned && !tab.url.startsWith('chrome://')) {
chrome.tabs.remove(tab.id);
console.log(`DevSync: Closing distraction tab: ${tab.url}`);
chrome.tabs.remove(tab.id, () => {
if (chrome.runtime.lastError) {
console.warn(`DevSync: Error closing tab (ignoring): ${chrome.runtime.lastError.message}`);
}
});
}
}
} catch (e) {
Expand All @@ -75,22 +218,24 @@ function closeUnwantedTabs() {
}

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));

const domain = url.hostname;
const isAllowed = ALLOWED_DOMAINS.some(allowedDomain =>
domain === allowedDomain || domain.endsWith('.' + allowedDomain)
);
if (!isAllowed && !details.url.startsWith('chrome://')) {
console.log(`Blocking navigation to: ${details.url}`);
chrome.tabs.remove(details.tabId);
chrome.tabs.remove(details.tabId, () => {
if (chrome.runtime.lastError) {
console.warn(`DevSync: Error blocking navigation (ignoring): ${chrome.runtime.lastError.message}`);
}
});
}
}
});
}, { url: [{ hostContains: '.' }] });
}, { url: [{ hostContains: '.' }] });
108 changes: 108 additions & 0 deletions extension/content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
let timerDiv = null;
let removalTimeout = null;
const TIMER_DIV_ID = 'devsync-floating-timer';
let timeSpan = null;
let sessionSpan = null;

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'startTimer') {
createOrUpdateTimerUI(message.endTime, message.sessionType, message.sessionCount);
}
else if (message.action === 'updateTime') {
chrome.storage.local.get(['timerEndTime', 'timerSessionType', 'timerSessionCount'], (data) => {
if(data.timerEndTime) {
updateTimerText(data.timerEndTime, data.timerSessionType, data.timerSessionCount);
}
});
}
else if (message.action === 'hideTimer' || message.action === 'stopTimer') {
removeTimerUI();
}

const isAppPage = window.location.host === "localhost:5173" ||
window.location.host === "your-devsync-domain.com";
if (isAppPage) {
window.postMessage({ type: "FROM_DEVSYNC_EXTENSION", payload: message }, "*");
}
sendResponse(true);
});

chrome.storage.local.get(['isRunning', 'timerEndTime', 'timerSessionType', 'timerSessionCount'], (result) => {
if (result.isRunning && result.timerEndTime) {
if (result.timerEndTime > Date.now()) {
createOrUpdateTimerUI(result.timerEndTime, result.timerSessionType, result.timerSessionCount);
}
}
});

function createOrUpdateTimerUI(endTime, sessionType, sessionCount) {
if (removalTimeout) {
clearTimeout(removalTimeout);
removalTimeout = null;
}

if (!timerDiv) {
timerDiv = document.createElement('div');
timerDiv.id = TIMER_DIV_ID;
timeSpan = document.createElement('span');
timeSpan.className = 'devsync-time';
sessionSpan = document.createElement('span');
sessionSpan.className = 'devsync-session';
timerDiv.appendChild(timeSpan);
timerDiv.appendChild(sessionSpan);
document.body.appendChild(timerDiv);
}

if (sessionType === 'work') {
timerDiv.classList.add('devsync-timer-work');
timerDiv.classList.remove('devsync-timer-break');
} else {
timerDiv.classList.add('devsync-timer-break');
timerDiv.classList.remove('devsync-timer-work');
}
timerDiv.classList.remove('devsync-timer-flashing');

updateTimerText(endTime, sessionType, sessionCount);
}

function updateTimerText(endTime, sessionType, sessionCount) {
if (!timerDiv) return;

const now = Date.now();
const remaining = endTime - now;
const totalSeconds = Math.ceil(remaining / 1000);

if (sessionType === 'work') {
sessionSpan.textContent = `Session ${sessionCount + 1}`;
} else {
sessionSpan.textContent = 'Break';
}

if (totalSeconds <= 0) {
timeSpan.textContent = '00:00';
timerDiv.classList.add('devsync-timer-flashing');
removalTimeout = setTimeout(removeTimerUI, 3000);
} else {
const minutes = Math.floor(totalSeconds / 60);
const seconds = Math.floor(totalSeconds % 60);
timeSpan.textContent =
`${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
}

function removeTimerUI() {
if (removalTimeout) {
clearTimeout(removalTimeout);
removalTimeout = null;
}
if (timerDiv) {
timerDiv.remove();
timerDiv = null;
timeSpan = null;
sessionSpan = null;
}
const existingDiv = document.getElementById(TIMER_DIV_ID);
if (existingDiv) {
existingDiv.remove();
}
}
Loading