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

Merge token renewal strategy #3

Merged
merged 13 commits into from
Apr 2, 2024
136 changes: 91 additions & 45 deletions extension/src/pages/popup/hooks/use-screenshot.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,118 @@ import { useRef, useState } from "react";
const useScreenshot = () => {
const canvas = useRef<HTMLCanvasElement | null>(null);
const [capturing, setCapturing] = useState(false);
const [isError, setIsError] = useState(false);

async function captureFullPageScreenshot(
const captureFullPageScreenshot = async (
callback: (dataUrl: string) => void
) {
) => {
if (capturing) {
return;
}

setIsError(false);
setCapturing(true);
const dataUrls: string[] = [];

const activeTab = await new Promise<chrome.tabs.Tab | undefined>(
(resolve) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
resolve(tabs[0]);
});
}
);

if (!activeTab?.id) {
return;
}

await chrome.scripting.executeScript({
target: { tabId: activeTab.id },
function: async () => {
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
await new Promise((resolve) => setTimeout(resolve, 100));
},
});

async function captureScreenshot() {
const screenshot = await new Promise<string>((resolve) => {
chrome.tabs.captureVisibleTab({ format: "png" }, (dataUrl) => {
resolve(dataUrl);
});
});

dataUrls.push(screenshot);
try {
const activeTab = await new Promise<chrome.tabs.Tab | undefined>(
(resolve) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
resolve(tabs[0]);
});
}
);

if (!activeTab?.id) {
return;
}

// Delete "fixed" CSS properties to avoid overlapping elements
await chrome.scripting.executeScript({
target: { tabId: activeTab.id },
function: () => window.scrollBy(0, window.innerHeight),
function: async () => {
const elements = document.querySelectorAll("*");

for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const style = window.getComputedStyle(element);

if (style["position"] === "fixed") {
(element as HTMLElement).setAttribute(
"style",
"position:static !important"
);
(element as HTMLElement).setAttribute(
"job-down-static",
"active"
);
}
}

await new Promise((resolve) => setTimeout(resolve, 200));
},
});

const reachedBottom = await chrome.scripting.executeScript({
await chrome.scripting.executeScript({
target: { tabId: activeTab.id },
function: () =>
window.scrollY >= document.body.scrollHeight - window.innerHeight,
function: async () => {
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
await new Promise((resolve) => setTimeout(resolve, 200));
},
});

if (reachedBottom[0].result) {
await stitchScreenshots(dataUrls, callback);
} else {
await new Promise((resolve) => setTimeout(resolve, 500));
captureScreenshot();
}
}
const captureScreenshot = async () => {
const screenshot = await new Promise<string>((resolve) => {
chrome.tabs.captureVisibleTab({ format: "png" }, (dataUrl) => {
resolve(dataUrl);
});
});

await captureScreenshot();
setCapturing(false);
}
dataUrls.push(screenshot);

if (!activeTab?.id) {
return;
}

await chrome.scripting.executeScript({
target: { tabId: activeTab.id },
function: () => window.scrollBy(0, window.innerHeight),
});

const reachedBottom = await chrome.scripting.executeScript({
target: { tabId: activeTab.id },
function: () =>
window.scrollY >= document.body.scrollHeight - window.innerHeight,
});

if (reachedBottom[0].result) {
await chrome.scripting.executeScript({
target: { tabId: activeTab.id },
function: async () => {
const staticElements = document.querySelectorAll(
'[job-down-static="active"]'
);
staticElements.forEach((element) => {
element.removeAttribute("style");
element.removeAttribute("job-down-static");
});
},
});

await stitchScreenshots(dataUrls, callback);
} else {
await new Promise((resolve) => setTimeout(resolve, 500));
await captureScreenshot();
}
};

await captureScreenshot();
setCapturing(false);
} catch (error) {
setIsError(true);
setCapturing(false);
}
};

async function stitchScreenshots(
dataUrls: string[],
Expand Down Expand Up @@ -114,7 +160,7 @@ const useScreenshot = () => {
);
}

return { captureFullPageScreenshot, canvasRef: canvas };
return { captureFullPageScreenshot, canvasRef: canvas, capturing, isError };
};

export default useScreenshot;
22 changes: 22 additions & 0 deletions extension/src/pages/popup/hooks/use-visible.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useSyncExternalStore } from "react";

export const useVisible = () => {
const visibilitySubscription = (callback: () => void) => {
document.addEventListener("visibilitychange", callback);

return () => {
document.removeEventListener("visibilitychange", callback);
};
};

const getVisibilitySnapshot = () => {
return document.visibilityState;
};

const visibilityState = useSyncExternalStore(
visibilitySubscription,
getVisibilitySnapshot
);

return visibilityState === "visible";
};
13 changes: 13 additions & 0 deletions extension/src/pages/popup/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import {
import { useEffect } from "react";
import { PlusSquare } from "lucide-react";
import { env } from "@src/env";
import { useVisible } from "../hooks/use-visible.hook";

export const Route = createRootRoute({
component: () => {
const visibilityChange = useVisible();
const { callAsync: getTokenAsync } = useMessage({
type: "userToken",
});
const { data: user, isPending, error } = useMessage({ type: "user" });
const { callAsync: signOutAsync } = useMessage(
{ type: "signOut" },
Expand Down Expand Up @@ -43,6 +48,14 @@ export const Route = createRootRoute({
}
}, [user]);

// Grab a new user token when the visibility changes to
// avoid having a stale token each time the popup is opened.
useEffect(() => {
if (visibilityChange) {
getTokenAsync({ type: "userToken" });
}
}, [visibilityChange]);

if (isPending) {
return <LoadingDialog isLoading={true}>Authenticating</LoadingDialog>;
}
Expand Down
71 changes: 64 additions & 7 deletions extension/src/pages/popup/routes/jobs/add.lazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,80 @@ import {
LoadingDialog,
useAddJob,
ScrollArea,
AlertDialog,
AlertDialogContent,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogFooter,
AlertDialogAction,
AlertDialogDescription,
} from "@application-tracker/frontend";
import { createLazyFileRoute } from "@tanstack/react-router";
import useMessage from "@pages/popup/hooks/use-message.hook";
import useScreenshot from "@pages/popup/hooks/use-screenshot.hook";
import { Aperture, Loader2 } from "lucide-react";
import { useState } from "react";
import { useEffect, useState } from "react";

export const Route = createLazyFileRoute("/jobs/add")({
component: AddJob,
});

function AddJob() {
const [capturing, setCapturing] = useState(false);
const { captureFullPageScreenshot, canvasRef } = useScreenshot();
const [currentUrl, setCurrentUrl] = useState<string>("");
const { captureFullPageScreenshot, canvasRef, isError, capturing } =
useScreenshot();

const { data: token } = useMessage({ type: "userToken" });
const { onSubmit, onClose, setJobImage, jobImage, isPending } =
useAddJob(token);

const onCapture = () => {
setCapturing(true);
const onCapture = async () => {
captureFullPageScreenshot((dataUrl) => {
setJobImage(dataUrl);
setCapturing(false);
});
};

const retrieveCurrentUrl = async () => {
const activeTab = await new Promise<chrome.tabs.Tab | undefined>(
(resolve) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
resolve(tabs[0]);
});
}
);

if (!activeTab?.url) {
return;
}

setCurrentUrl(activeTab.url);
};

useEffect(() => {
if (currentUrl) {
return;
}
(async () => {
await retrieveCurrentUrl();
})();
}, [retrieveCurrentUrl, currentUrl]);

return (
<>
<div className="flex flex-col items-center justify-center">
<ScrollArea className="h-[455px]" type="always">
<div className="my-2 flex items-center mx-4">
<JobForm.JobForm onSubmit={onSubmit}>
<JobForm.JobForm
key={currentUrl}
onSubmit={onSubmit}
defaultValues={{
position: "",
company: "",
status: "applied",
notes: "",
url: currentUrl,
}}
>
<JobForm.JobFormFooter>
<div className="fixed bottom-0 left-0 z-10 flex w-full justify-between border border-t bg-white px-8 py-4">
<Button type="submit">Add</Button>
Expand All @@ -60,6 +101,22 @@ function AddJob() {
</ScrollArea>
</div>
<LoadingDialog isLoading={isPending}>Adding job</LoadingDialog>
{isError && (
<AlertDialog defaultOpen>
<AlertDialogContent className="max-w-[350px] mx-auto">
<AlertDialogHeader>
<AlertDialogTitle>Capture Error</AlertDialogTitle>
<AlertDialogDescription>
An error occurred while attempting to capture your current
webpage. Ensure you are on a valid webpage and try again.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction>Continue</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
<canvas ref={canvasRef} className="hidden" />
</>
);
Expand Down
Loading
Loading