Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block www using *, Add context menu to block entire website #75

Merged
merged 2 commits into from
Apr 25, 2024
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ _All Time_, _This Month_, _This Week_, or _Today_.
### Examples

```
example.com # Blocks example.com/ and any page on it


example.com/ # Blocks example.com/ ONLY
example.com/* # Blocks example.com/ and any page on it

Expand All @@ -54,6 +57,13 @@ example.com/*rry/* # Blocks e.g.:
# - example.com/strawberry/projects/1
```

## Context menu

Right-click on any website, see context menu Block Site, with options:

- Block this page only
- Block entire website

## Privacy notice

Block Site doesn't collect any personal information or data.
Expand Down
58 changes: 47 additions & 11 deletions src/helpers/__tests__/find-rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ describe("findRule()", () => {

describe("domains", () => {
it("can block main domain only", () => {
expect(findRule("https://example.com/", ["example.com"])).toBeUndefined();
expect(findRule("https://example.com/", ["example.com/"])).toEqual<Rule>({
type: "block",
path: "example.com/",
Expand All @@ -50,7 +49,6 @@ describe("findRule()", () => {
});

it("can block subdomain only", () => {
expect(findRule("https://dashboard.example.com/", ["dashboard.example.com"])).toBeUndefined();
expect(findRule("https://dashboard.example.com/", ["dashboard.example.com/"])).toEqual<Rule>({
type: "block",
path: "dashboard.example.com/",
Expand All @@ -71,6 +69,20 @@ describe("findRule()", () => {

expect(findRule("https://example.com/", ["*.example.com/"])).toBeUndefined();
});

it("can block www subdomain with *", () => {
const blocked = ["*.facebook.com/*", "www.facebook.com/*"];
blocked.forEach((blocked) => {
expect(findRule("https://www.facebook.com/", [blocked])).toEqual<Rule>({
type: "block",
path: blocked,
});
});

blocked.forEach((blocked) => {
expect(findRule("https://facebook.com/", [blocked])).toBeUndefined();
});
});
});

describe("? in domains", () => {
Expand All @@ -94,21 +106,45 @@ describe("findRule()", () => {
});

describe("paths", () => {
it("requires trailing /", () => {
expect(findRule("https://example.com/", ["example.com"])).toBeUndefined();
expect(findRule("https://example.com/", ["example.com/"])).toEqual<Rule>({
type: "block",
path: "example.com/",
it("expands with www. if path does not start with www. or *.", () => {
const blocked = ["facebook.com/*", "*.facebook.com/*", "www.facebook.com/*"];
blocked.forEach((blocked) => {
expect(findRule("https://www.facebook.com/", [blocked])).toEqual<Rule>({
type: "block",
path: blocked,
});
});
});

it("expands with /* if there is no /", () => {
[
"https://example.com/",
"https://example.com/pear/projects/1",
].forEach((url) => {
expect(findRule(url, ["example.com"])).toEqual<Rule>({
type: "block",
path: "example.com",
});
});

[
"https://dashboard.example.com/",
"https://dashboard.example.com/apples/",
].forEach((url) => {
expect(findRule(url, ["dashboard.example.com"])).toEqual<Rule>({
type: "block",
path: "dashboard.example.com",
});
});

expect(findRule("https://example.com/projects/1", ["example.com/projects"])).toBeUndefined();
});

it("does not block any subpath automatically (without *)", () => {
const urls = [
[
"https://example.com/apple/",
"https://example.com/apple/dashboard?tab=analytics#charts",
];

urls.forEach((url) => expect(findRule(url, ["example.com/"])).toBeUndefined());
].forEach((url) => expect(findRule(url, ["example.com/"])).toBeUndefined());
});

describe("* in paths", () => {
Expand Down
6 changes: 6 additions & 0 deletions src/helpers/__tests__/make-rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import makeRules, { Rule } from "../make-rules";
test("makeRules()", () => {
expect(
makeRules([
"www.facebook.com",
"https://www.instagram.com/",

"*.youtube.com",
"!music.youtube.com",

Expand All @@ -13,6 +16,9 @@ test("makeRules()", () => {
{ type: "allow", path: "music.youtube.com" },
{ type: "allow", path: "reddit.com/r/MachineLearning" },

{ type: "block", path: "www.facebook.com" },
{ type: "block", path: "www.instagram.com/" },

{ type: "block", path: "*.youtube.com" },
{ type: "block", path: "reddit.com" },
]);
Expand Down
35 changes: 0 additions & 35 deletions src/helpers/__tests__/normalize-url.test.ts

This file was deleted.

35 changes: 35 additions & 0 deletions src/helpers/__tests__/remove-protocol.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import removeProtocol from "../remove-protocol";

describe("removeProtocol()", () => {
it("removes https, http", () => {
expect(removeProtocol("https://example.com/")).toBe("example.com/");
expect(removeProtocol("http://example.com/")).toBe("example.com/");

expect(removeProtocol("https://www.example.com/")).toBe("www.example.com/");
expect(removeProtocol("http://www.example.com/")).toBe("www.example.com/");

expect(removeProtocol("https://dashboard.example.com/")).toBe("dashboard.example.com/");
expect(removeProtocol("http://dashboard.example.com/")).toBe("dashboard.example.com/");

expect(removeProtocol("https://www.dashboard.example.com/")).toBe("www.dashboard.example.com/");
expect(removeProtocol("http://www.dashboard.example.com/")).toBe("www.dashboard.example.com/");
});

it("keeps path unchanged", () => {
expect(removeProtocol("https://www.example.com/apple/projects/1?tab=analytics#charts")).toBe(
"www.example.com/apple/projects/1?tab=analytics#charts",
);

expect(removeProtocol("https://example.com/apple/projects/1?tab=analytics#charts")).toBe(
"example.com/apple/projects/1?tab=analytics#charts",
);

expect(removeProtocol("https://example.com/apple/projects/1")).toBe(
"example.com/apple/projects/1",
);

expect(removeProtocol("https://example.com/apple/projects/")).toBe(
"example.com/apple/projects/",
);
});
});
45 changes: 35 additions & 10 deletions src/helpers/find-rule.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
import normalizeUrl from "./normalize-url";
import removeProtocol from "./remove-protocol";
import makeRules, { Rule } from "./make-rules";

const expandPath = (path: string) => {
const expanded = [path];
if (!["*.", "www."].find((prefix) => path.startsWith(prefix))) {
expanded.push(`www.${path}`);
}

[...expanded].forEach((path) => {
if (!path.includes("/")) {
expanded.push(`${path}/*`);
}
});

return expanded;
};

export default (url: string, blocked: string[]): Rule | undefined => {
const normalizedUrl = normalizeUrl(url);
const normalizedUrl = removeProtocol(url);
const rules = makeRules(blocked);

const foundRule = rules.find((rule) =>
normalizedUrl.match(new RegExp(
"^"
+ rule.path
.replace(/\?/g, ".") // user can type "?" to match any one character
.replace(/\*/g, ".*") // user can type "*" to match any zero or more characters
+ "$",
)));
const foundRule = rules.find(({ path }) => {
const patterns = expandPath(path)
.map((path) => path.replace(/[.+]/g, "\\$&")) // escape regex characters
.map((path) => (
"^"
+ path
.replace(/\?/g, ".") // user can type "?" to match any one character
.replace(/\*/g, ".*") // user can type "*" to match any zero or more characters
+ "$"
));

const found = patterns.some((pattern) => {
const matches = normalizedUrl.match(new RegExp(pattern));
return matches;
});

return found;
});

return foundRule;
};
6 changes: 3 additions & 3 deletions src/helpers/make-rules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import normalizeUrl from "./normalize-url";
import removeProtocol from "./remove-protocol";

type RuleType = "allow" | "block"

Expand All @@ -10,11 +10,11 @@ export interface Rule {
export default (blocked: string[]): Rule[] => {
const allowList = blocked
.filter((item) => item.startsWith("!"))
.map((item) => normalizeUrl(item.substring(1)));
.map((item) => removeProtocol(item.substring(1)));

const blockList = blocked
.filter((item) => !item.startsWith("!"))
.map(normalizeUrl);
.map(removeProtocol);

return [
...allowList.map((path) => ({ type: "allow", path } as Rule)),
Expand Down
7 changes: 0 additions & 7 deletions src/helpers/normalize-url.ts

This file was deleted.

27 changes: 18 additions & 9 deletions src/helpers/recreate-context-menu.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import storage from "../storage";
import blockSite from "./block-site";
import normalizeUrl from "./normalize-url";
import removeProtocol from "./remove-protocol";

const createContextMenu = () => {
const parentId = chrome.contextMenus.create({
Expand All @@ -9,24 +9,33 @@ const createContextMenu = () => {
documentUrlPatterns: ["https://*/*", "http://*/*"],
});

const blockThisSiteId = "block_this_site";
const blockOneId = "block_one";
chrome.contextMenus.create({
parentId,
id: blockThisSiteId,
title: "Block this site",
id: blockOneId,
title: "Block this page only",
});

const blockAllId = "block_all";
chrome.contextMenus.create({
parentId,
id: blockAllId,
title: "Block entire website",
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
const tabId = tab?.id;
if (!tabId || info.menuItemId !== blockThisSiteId) {
if (!tabId || ![blockOneId, blockAllId].includes(String(info.menuItemId))) {
return;
}

storage.get(["blocked"]).then(({ blocked }) => {
const url = info.pageUrl;
const normalizedUrl = normalizeUrl(url);
const updatedBlocked = [...blocked, normalizedUrl];
const url = info.pageUrl;
const blockedUrl = info.menuItemId === blockOneId
? removeProtocol(url)
: new URL(url).host;

storage.get(["blocked"]).then(({ blocked }) => {
const updatedBlocked = [...blocked, blockedUrl];
storage.set({ blocked: updatedBlocked });
blockSite({ blocked: updatedBlocked, tabId, url });
});
Expand Down
1 change: 1 addition & 0 deletions src/helpers/remove-protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default (url: string): string => url.replace(/^http(s?):\/\//, "");
Loading