Skip to content
Open
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
1 change: 1 addition & 0 deletions extension/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Impulse Blocker</title>
<link rel="stylesheet" href="dist/options.css" />
</head>
<body>
<div id="root"></div>
Expand Down
1 change: 1 addition & 0 deletions extension/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Impulse Blocker</title>
<link rel="stylesheet" href="dist/popup.css" />
</head>
<body>
<div id="root"></div>
Expand Down
24,423 changes: 11,318 additions & 13,105 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,34 @@
"scripts": {
"watch": "webpack --mode=development --watch",
"build": "webpack --mode=development",
"release": "webpack --mode=development && web-ext build --source-dir extension --config web-ext-config.js",
"browser": "web-ext run --browser-console --source-dir extension",
"release": "webpack --mode=development && web-ext build --source-dir extension --config web-ext-config.mjs",
"browser": "web-ext run --browser-console --source-dir extension --devtools",
"test": "jest"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@types/jest": "^26.0.20",
"babel-jest": "^26.6.3",
"babel-jest": "^30.2.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^1.0.1",
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The webpack has been upgraded from v4 to v5, but the clean-webpack-plugin is still at v1.0.1 which is not compatible with webpack 5. This will cause build errors. The plugin should be updated to v3.0.0 or later, or you should use webpack 5's built-in asset cleaning feature by setting 'output.clean: true' in webpack.config.js.

Suggested change
"clean-webpack-plugin": "^1.0.1",
"clean-webpack-plugin": "^3.0.0",

Copilot uses AI. Check for mistakes.
"core-js": "^3.8.2",
"css-loader": "^2.1.1",
"css-loader": "^7.1.2",
"eslint": "^7.17.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.3",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"file-loader": "^3.0.1",
"jest": "^26.6.3",
"file-loader": "^6.2.0",
"jest": "^30.2.0",
"react-test-renderer": "^16.14.0",
"style-loader": "^0.23.1",
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The style-loader is still at v0.23.1, which is incompatible with webpack 5. This package should be updated to v3.0.0 or later for webpack 5 compatibility. However, if the intention is to extract CSS into separate files (as suggested by the HTML changes), this should be replaced with mini-css-extract-plugin instead.

Suggested change
"style-loader": "^0.23.1",
"style-loader": "^3.0.0",

Copilot uses AI. Check for mistakes.
"web-ext": "^5.5.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12"
"web-ext": "^9.2.0",
"webpack": "^5.104.1",
"webpack-cli": "^6.0.1"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
Expand Down
101 changes: 86 additions & 15 deletions src/ImpulseBlocker.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import dayjs from 'dayjs';
import dayjs from "dayjs";

import extensionStatus from './enums/extensionStatus';
import Website from './storage/Website';
import PopupIcon from './PopupIcon';
import { createMatchPatterns, redirectToBlockedPage } from './utils/functions';
import extensionStatus from "./enums/extensionStatus";
import Website from "./storage/Website";
import PopupIcon from "./PopupIcon";
import { createMatchPatterns, redirectToBlockedPage } from "./utils/functions";

class ImpulseBlocker {
constructor(storageHandler) {
Expand All @@ -16,6 +16,8 @@ class ImpulseBlocker {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.getBlockedDomains = this.getBlockedDomains.bind(this);
this.refreshBlockedTabs = this.refreshBlockedTabs.bind(this);
this.enableBlockedTabs = this.enableBlockedTabs.bind(this);
}

boot() {
Expand All @@ -39,7 +41,7 @@ class ImpulseBlocker {

const pausedUntilParsed = dayjs(pausedUntil);

const differenceFromNow = pausedUntilParsed.diff(dayjs(), 'second');
const differenceFromNow = pausedUntilParsed.diff(dayjs(), "second");

if (differenceFromNow < 0) {
return this.start(false);
Expand All @@ -61,8 +63,8 @@ class ImpulseBlocker {
if (domainsToBlock.length > 0) {
browser.webRequest.onBeforeRequest.addListener(
redirectToBlockedPage,
{ urls: domainsToBlock, types: ['main_frame'] },
['blocking'],
{ urls: domainsToBlock, types: ["main_frame"] },
["blocking"],
);
}

Expand All @@ -72,15 +74,15 @@ class ImpulseBlocker {

onStorageUpdated(changes) {
if (changes.sites === undefined) {
return Promise.resolve('No need for action');
return Promise.resolve("No need for action");
}

return this.storageHandler.getStatus().then(({ status }) => {
if (status === extensionStatus.ON) {
return this.attachWebRequestListener();
}

return Promise.resolve('No need for action');
return Promise.resolve("No need for action");
});
}

Expand All @@ -89,8 +91,11 @@ class ImpulseBlocker {
await this.storageHandler.setStatus(extensionStatus.OFF);
}

await browser.webRequest.onBeforeRequest.removeListener(redirectToBlockedPage);
await browser.webRequest.onBeforeRequest.removeListener(
redirectToBlockedPage,
);
await PopupIcon.off();
await this.enableBlockedTabs();
}

async start(setStatus = true) {
Expand All @@ -100,19 +105,84 @@ class ImpulseBlocker {

await this.attachWebRequestListener();
await PopupIcon.on();
await this.refreshBlockedTabs();
}

// Refreshes all tabs which are in the block list
// This triggers the Impulse Blocker window if they should be blocked
// Intended when starting the blocker or coming back from a pause
async refreshBlockedTabs() {
// Get a list of open tabs which are in the block list
const allTabs = await browser.tabs.query({});
const blockedDomains = await this.getBlockedDomains();
const tabsToRefresh = allTabs.filter((tab) => {
if (!tab.url) {
return false;
}
try {
const tabUrl = new URL(tab.url);
const tabDomain = tabUrl.hostname.replace(/^www\./, "");

return blockedDomains.some((blockedDomain) => {
const cleanedBlockedDomain = blockedDomain.replace(/^www\./, "");
return (
tabDomain === cleanedBlockedDomain ||
tabDomain.endsWith(`.${cleanedBlockedDomain}`)
);
});
} catch (e) {
// Invalid URL, skip this tab
return false;
}
});

// Refresh each tab with a blocked domain
tabsToRefresh.forEach((tab) => {
browser.tabs.reload(tab.id);
});
}

// Finds all active Impulse Blocker windows (sites in a blocked state) and replaces them with the site they are blocking
// Intended for automatically removing all the blocks from windows (during a pause or after turning blocker off)
async enableBlockedTabs() {
const allTabs = await browser.tabs.query({});
allTabs.forEach((tab) => {
if (!tab.url) {
return;
}
try {
const tabUrl = new URL(tab.url);
if (tabUrl.protocol === "moz-extension:") {
if (!tabUrl.searchParams.has("target")) {
return;
}
const target = tabUrl.searchParams.get("target");
browser.tabs.update(
tab.id,
{
loadReplace: true,
url: target,
},
);
}
} catch (e) {
return;
}
});
}

async pause(duration = 60 * 5, setStatus = true) {
browser.webRequest.onBeforeRequest.removeListener(redirectToBlockedPage);

const pausedUntil = dayjs().add(duration, 'seconds');
const pausedUntil = dayjs().add(duration, "seconds");

if (setStatus) {
await this.storageHandler.setStatus(extensionStatus.PAUSED);
}

await this.storageHandler.setPausedUntil(pausedUntil);
await PopupIcon.off();
await this.enableBlockedTabs();

setTimeout(() => {
this.start();
Expand All @@ -128,7 +198,8 @@ class ImpulseBlocker {
}

getBlockedDomains() {
return this.storageHandler.getBlockedWebsites()
return this.storageHandler
.getBlockedWebsites()
.then((storage) => storage.sites.map((website) => website.domain));
}

Expand Down Expand Up @@ -159,7 +230,7 @@ class ImpulseBlocker {
}

addToBlockList(domain) {
const domainToBlock = domain.replace(/.*www\./, '');
const domainToBlock = domain.replace(/.*www\./, "");

return this.storageHandler.getBlockedWebsites().then(({ sites }) => {
const updatedWebsites = [...sites, Website.create(domainToBlock)];
Expand All @@ -169,7 +240,7 @@ class ImpulseBlocker {
}

removeFromBlockList(domain) {
const domainToRemove = domain.replace(/.*www\./, '');
const domainToRemove = domain.replace(/.*www\./, "");

return this.storageHandler.getBlockedWebsites().then((storage) => {
const updatedWebsites = storage.sites.filter(
Expand Down
1 change: 0 additions & 1 deletion src/options/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './options.css';

import MessageTypes from '../enums/messages';
import DomainListItem from './components/DomainListItem';
Expand Down
4 changes: 4 additions & 0 deletions src/popup/components/Pause/PauseSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export default class PauseSection extends React.Component {

const secondsToExpire = expiresAt.diff(currentDatetime, 'second');

if (secondsToExpire <= 0) {
clearInterval(this.countdownTimer);
this.props.onChange();
}
this.setState({ secondsToExpire });
}

Expand Down
1 change: 0 additions & 1 deletion src/popup/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './popup.css';
import cogs from './cogs.svg';

import ExtensionStatus from './components/ExtensionStatus';
Expand Down
2 changes: 1 addition & 1 deletion web-ext-config.js → web-ext-config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
export default {
ignoreFiles: [
'package.json',
'package-lock.json',
Expand Down