Skip to content

Commit

Permalink
refactor: ♻️ better readability
Browse files Browse the repository at this point in the history
  • Loading branch information
AlecBlance committed Oct 19, 2024
1 parent e2924d4 commit 841f816
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 163 deletions.
193 changes: 31 additions & 162 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -1,178 +1,47 @@
import * as cheerio from "cheerio";
import { IBucketType } from "./types";
import { getStorageKeyValue } from "@/utils/helper.utils";
import { recordBuckets } from "./lib/recorder";

const requests = chrome.webRequest.onHeadersReceived;
const storage = chrome.storage.local;

/**
* Gets the buckets stored in the session storage
*/
const getBuckets = async () => {
const { buckets } = await storage.get("buckets");
return buckets as IBucketType;
// To make async function work, and also the ability to remove listener
const dummyFunc = (detail: chrome.webRequest.WebResponseHeadersDetails) => {
recordBuckets(detail);
};

/**
* Changes the badge text to the number of buckets
*
* @param buckets list of buckets from the storage
*/
const addNumber = async (buckets: IBucketType) => {
const { lastSeen } = await storage.get("lastSeen");
const bucketsListed =
buckets.good.filter((bucket) => bucket.date > lastSeen.good).length + 1;
chrome.action.setBadgeText({
text: bucketsListed.toString(),
});
chrome.action.setBadgeBackgroundColor({
color: "green",
});
};

/**
* Gets the permissions from the offscreen page
*
* @param $ the cheerio object
*/
const getPerms = ($: cheerio.CheerioAPI, hostname: string) => {
const permissions = {} as Record<string, string[]>;
const hasUri = $("URI");
const hasCode = $("Code");
const hasListBucket = $("ListBucketResult");
let type = "";
const date = new Date().getTime();
try {
if (!hasUri.length && !hasCode.length && hasListBucket)
throw new Error("No permissions");
if (hasUri.length) {
hasUri.toArray().map((elem) => {
const title = $(elem).text().split("/").pop()!;
const perm = $(elem).parent().next().text();
permissions[title] = [...(permissions[title] || []), perm];
});
type = "good";
}
if (hasListBucket.length) {
permissions["ListBucket"] = ["True"];
type = "good";
} else if (hasCode.length) {
const elem = hasCode[0];
const title = $(elem).text();
const perm = $(elem).next().text();
type = "bad";
permissions[title] = [...(permissions[title] || []), perm];
}
} catch (e) {
type = "error";
console.log(e);
}
return { type, info: { permissions, date, hostname } };
const listener = async (toRecord: boolean) => {
if (toRecord)
requests.addListener(
dummyFunc,
{
urls: ["<all_urls>"],
},
["responseHeaders"],
);
else requests.removeListener(dummyFunc);
};

/**
* Gets the bucket info from the offscreen page
*
* @param hostname the hostname of the bucket
*/
const getBucketInfo = async (hostname: string, buckets: IBucketType) => {
try {
const text = await Promise.all([
fetch("http://" + hostname + "/?acl").then((res) => res.text()),
fetch("http://" + hostname + "/").then((res) => res.text()),
]).then(([aclText, listBucketText]) => aclText + listBucketText);
const permissions = getPerms(cheerio.load(text), hostname);
(async () => {
const [{ buckets }, { record }] = (await getStorageKeyValue([
"buckets",
"record",
])) as Record<string, any>[];
if (!buckets || !Object.keys(buckets).length) {
storage.set({
buckets: {
...buckets,
[permissions.type]: [permissions.info, ...buckets[permissions.type]],
},
buckets: { good: [], bad: [], error: [] },
record: true,
lastSeen: { good: 0, bad: 0, error: 0 },
});
return permissions;
} catch (e) {
console.log(e);
return false;
}
};
listener(record ?? true);
})();

/**
* Records the buckets from the network requests
*
* @param response the response from the network request
*/
const recordBuckets = (
response: chrome.webRequest.WebResponseHeadersDetails,
): void => {
const s3 = response.responseHeaders?.some(
(header) => header.name == "x-amz-request-id",
);
if (!s3) return;
getBuckets().then((buckets) => {
const url = new URL(response.url);
let hostname = url.hostname;
const pathname = url.pathname;
if (hostname === "s3.amazonaws.com") {
hostname += "/" + pathname.split("/")[1];
}
const isPresent = Object.values(buckets)
.flat()
.some((bucket) => bucket.hostname === hostname);
const noFavicon = !hostname.includes("favicon.ico");
if (!isPresent && noFavicon) {
getBucketInfo(hostname, buckets).then((permissions) => {
if (permissions && permissions.type === "good") addNumber(buckets);
});
}
chrome.runtime.onMessage.addListener((toRecord: boolean) => {
listener(toRecord);
chrome.action.setBadgeText({
text: toRecord ? "" : "!",
});
};

/**
* Listens for the network requests
*/
const listener = async () => {
requests.addListener(
recordBuckets,
{
urls: ["<all_urls>"],
},
["responseHeaders"],
);
};

/**
* Handles messages from the popup
*
* @param toRecord whether to record the buckets
* @param _sender the sender of the message
* @param sendResponse the response to send back
*/
const fromPopup = (toRecord: boolean) => {
if (toRecord) {
listener();
chrome.action.setBadgeText({
text: "",
});
} else {
requests.removeListener(recordBuckets);
chrome.action.setBadgeText({
text: "!",
});
chrome.action.setBadgeBackgroundColor({
color: "orange",
});
}
};

listener();

getStorageKeyValue("buckets").then((response: Record<string, any>) => {
if (Object.keys(response).length && Object.keys(response.buckets).length)
return;
storage.set({
buckets: { good: [], bad: [], error: [] },
record: true,
lastSeen: { good: 0, bad: 0, error: 0 },
chrome.action.setBadgeBackgroundColor({
color: toRecord ? "green" : "orange",
});
});

chrome.runtime.onMessage.addListener(fromPopup);
104 changes: 104 additions & 0 deletions src/lib/recorder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as cheerio from "cheerio";
import { addNumber } from "@/utils/helper.utils";
import { IBucketType } from "@/types";

const storage = chrome.storage.local;
//const preRecorded = new Set();

/**
* Gets the permissions from the offscreen page
*
* @param $ the cheerio object
*/
const getPerms = ($: cheerio.CheerioAPI, hostname: string) => {
const permissions = {} as Record<string, string[]>;
const hasUri = $("URI");
const hasCode = $("Code");
const hasListBucket = $("ListBucketResult");
let type = "";
const date = new Date().getTime();
console.log("Date", date);
console.log("Hostname", hostname);

try {
if (!hasUri.length && !hasCode.length && hasListBucket)
throw new Error("No permissions");
if (hasUri.length) {
hasUri.toArray().map((elem) => {
const title = $(elem).text().split("/").pop()!;
const perm = $(elem).parent().next().text();
permissions[title] = [...(permissions[title] || []), perm];
});
type = "good";
}
if (hasListBucket.length) {
permissions["ListBucket"] = ["True"];
type = "good";
} else if (hasCode.length) {
const elem = hasCode[0];
const title = $(elem).text();
const perm = $(elem).next().text();
type = "bad";
permissions[title] = [...(permissions[title] || []), perm];
}
} catch (e) {
type = "error";
console.log("Error in getPerms", e);
}
return { type, info: { permissions, date, hostname } };
};

/**
* Gets the bucket info from the offscreen page
*
* @param hostname the hostname of the bucket
*/
const getBucketInfo = async (hostname: string) => {
try {
// Combining results to have a single text to search
const text = await Promise.all([
fetch("http://" + hostname + "/?acl").then((res) => res.text()),
fetch("http://" + hostname + "/").then((res) => res.text()),
]).then(([aclText, listBucketText]) => aclText + listBucketText);
return getPerms(cheerio.load(text), hostname);
} catch (e) {
console.log(e);
return false;
}
};

/**
* Records the buckets from the network requests
*
* @param response the response from the network request
*/
export const recordBuckets = async (
response: chrome.webRequest.WebResponseHeadersDetails,
) => {
const s3 = response.responseHeaders?.some(
(header) => header.name == "x-amz-request-id",
);
if (!s3) return;
const { buckets }: { buckets: IBucketType } = await storage.get("buckets");
const { hostname, pathname } = new URL(response.url);
// There are instances that the bucket name is a path
const bucketHostname =
hostname === "s3.amazonaws.com"
? `${hostname}/${pathname.split("/")[1]}`
: hostname;
const isPresent = Object.values(buckets)
.flat()
.some((bucket) => bucket.hostname === bucketHostname);
// Double requests that might be recorded that has favicon.ico
const hasFavicon = bucketHostname.includes("favicon.ico");
if (isPresent || hasFavicon) return;
const bucketInfo = await getBucketInfo(bucketHostname);
if (!bucketInfo) return;
storage.set({
buckets: {
...buckets,
[bucketInfo.type]: [bucketInfo.info, ...buckets[bucketInfo.type]],
},
});
bucketInfo.type === "good" && addNumber(buckets);

Check failure on line 103 in src/lib/recorder.ts

View workflow job for this annotation

GitHub Actions / build

Expected an assignment or function call and instead saw an expression
};
22 changes: 22 additions & 0 deletions src/utils/helper.utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { IBucketType } from "@/types";

const storage = chrome.storage.local;

/**
* This gets the local storage key value or the keys within the local storage key object
*
Expand Down Expand Up @@ -52,3 +56,21 @@ export const editStorageKeyValue = async (
}),
);
};

/**
* Changes the badge text to the number of buckets
*
* @param buckets list of buckets from the storage
*/
export const addNumber = async (buckets: IBucketType) => {
const { lastSeen } = await storage.get("lastSeen");
// We only care for the good buckets
const bucketsListed =
buckets.good.filter((bucket) => bucket.date > lastSeen.good).length + 1;
chrome.action.setBadgeText({
text: bucketsListed.toString(),
});
chrome.action.setBadgeBackgroundColor({
color: "green",
});
};
2 changes: 1 addition & 1 deletion tsconfig.app.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/background.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/Bucket.tsx","./src/components/CustomSwitch.tsx","./src/components/Menu.tsx","./src/components/TabBuckets.tsx","./src/components/TabButton.tsx","./src/components/ui/accordion.tsx","./src/components/ui/badge.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/separator.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/lib/utils.ts","./src/store/useBuckets.store.ts","./src/store/useLastSeen.store.ts","./src/types/index.ts","./src/utils/helper.utils.ts"],"version":"5.6.2"}
{"root":["./src/App.tsx","./src/background.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/Bucket.tsx","./src/components/CustomSwitch.tsx","./src/components/Menu.tsx","./src/components/TabBuckets.tsx","./src/components/TabButton.tsx","./src/components/ui/accordion.tsx","./src/components/ui/badge.tsx","./src/components/ui/dropdown-menu.tsx","./src/components/ui/separator.tsx","./src/components/ui/switch.tsx","./src/components/ui/tabs.tsx","./src/lib/recorder.ts","./src/lib/utils.ts","./src/store/useBuckets.store.ts","./src/store/useLastSeen.store.ts","./src/types/index.ts","./src/utils/helper.utils.ts"],"version":"5.6.2"}

0 comments on commit 841f816

Please sign in to comment.