diff --git a/frontend/src/components/image/index.tsx b/frontend/src/components/image/index.tsx index 6af360d..76b24e0 100644 --- a/frontend/src/components/image/index.tsx +++ b/frontend/src/components/image/index.tsx @@ -1,5 +1,5 @@ -import { Component, createSignal } from "solid-js"; -import { base } from "../../network"; +import { Component, createResource, createSignal } from "solid-js"; +import { base, getAccessToken } from "../../network"; import { A } from "@solidjs/router"; import { Dialog } from "@kobalte/core"; @@ -11,11 +11,7 @@ type ImageComponentProps = { export const ImageComponent: Component = (props) => { const [isOpen, setIsOpen] = createSignal(false); - // TODO: make sure this is up to date. Put it behind a resource. - const accessToken = localStorage.getItem("access"); - if (accessToken == null) { - return <>Ermm... Access token is not set :( - } + const [accessToken] = createResource(getAccessToken); return ( <> diff --git a/frontend/src/contexts/Notifications.tsx b/frontend/src/contexts/Notifications.tsx index abe0160..02b50d5 100644 --- a/frontend/src/contexts/Notifications.tsx +++ b/frontend/src/contexts/Notifications.tsx @@ -2,154 +2,159 @@ import { InferOutput, safeParse } from "valibot"; import { useSearchImageContext } from "./SearchImageContext"; import { createStore } from "solid-js/store"; import { - Component, - createContext, - createEffect, - onCleanup, - ParentProps, - useContext, + Component, + createContext, + createEffect, + createResource, + onCleanup, + ParentProps, + useContext, } from "solid-js"; -import { base } from "@network/index"; +import { base, getAccessToken } from "@network/index"; import { - notificationValidator, - processingImagesValidator, - processingListValidator, + notificationValidator, + processingImagesValidator, + processingListValidator, } from "@network/notifications"; type NotificationState = { - ProcessingImages: Record< - string, - InferOutput | undefined - >; - ProcessingLists: Record< - string, - InferOutput | undefined - >; + ProcessingImages: Record< + string, + InferOutput | undefined + >; + ProcessingLists: Record< + string, + InferOutput | undefined + >; }; export const Notifications = (onCompleteImage: () => void) => { - const [state, setState] = createStore({ - ProcessingImages: {}, - ProcessingLists: {}, - }); + const [state, setState] = createStore({ + ProcessingImages: {}, + ProcessingLists: {}, + }); - const { processingImages } = useSearchImageContext(); + const { processingImages } = useSearchImageContext(); - const access = localStorage.getItem("access"); - if (access == null) { - throw new Error("Access token not defined"); - } + const [accessToken] = createResource(getAccessToken); - const dataEventListener = (e: MessageEvent) => { - if (typeof e.data !== "string") { - console.error("Error type is not string"); - return; - } + const dataEventListener = (e: MessageEvent) => { + if (typeof e.data !== "string") { + console.error("Error type is not string"); + return; + } - let jsonData: object = {}; - try { - jsonData = JSON.parse(e.data); - } catch (e) { - console.error(e); - return; - } + let jsonData: object = {}; + try { + jsonData = JSON.parse(e.data); + } catch (e) { + console.error(e); + return; + } - const notification = safeParse(notificationValidator, jsonData); - if (!notification.success) { - console.error("Processing image could not be parsed.", e.data); - return; - } + const notification = safeParse(notificationValidator, jsonData); + if (!notification.success) { + console.error("Processing image could not be parsed.", e.data); + return; + } - console.log("SSE: ", notification); + console.log("SSE: ", notification); - if (notification.output.Type === "image") { - const { ImageID, Status } = notification.output; + if (notification.output.Type === "image") { + const { ImageID, Status } = notification.output; - if (Status === "complete") { - setState("ProcessingImages", ImageID, undefined); - onCompleteImage(); - } else { - setState("ProcessingImages", ImageID, notification.output); - } - } else if (notification.output.Type === "list") { - const { ListID, Status } = notification.output; + if (Status === "complete") { + setState("ProcessingImages", ImageID, undefined); + onCompleteImage(); + } else { + setState("ProcessingImages", ImageID, notification.output); + } + } else if (notification.output.Type === "list") { + const { ListID, Status } = notification.output; - if (Status === "complete") { - setState("ProcessingLists", ListID, undefined); - onCompleteImage(); - } else { - setState("ProcessingLists", ListID, notification.output); - } - } - }; + if (Status === "complete") { + setState("ProcessingLists", ListID, undefined); + onCompleteImage(); + } else { + setState("ProcessingLists", ListID, notification.output); + } + } + }; - const upsertImageProcessing = ( - images: NotificationState["ProcessingImages"], - ) => { - setState("ProcessingImages", (currentImages) => ({ - ...currentImages, - ...images, - })); - }; + const upsertImageProcessing = ( + images: NotificationState["ProcessingImages"], + ) => { + setState("ProcessingImages", (currentImages) => ({ + ...currentImages, + ...images, + })); + }; - createEffect(() => { - const images = processingImages(); - if (images == null) { - return; - } + createEffect(() => { + const images = processingImages(); + if (images == null) { + return; + } - upsertImageProcessing( - Object.fromEntries( - images.map((i) => [ - i.ImageID, - { - Type: "image", - ImageID: i.ImageID, - ImageName: i.Image.ImageName, - Status: i.Status, - }, - ]), - ), - ); - }); + upsertImageProcessing( + Object.fromEntries( + images.map((i) => [ + i.ImageID, + { + Type: "image", + ImageID: i.ImageID, + ImageName: i.Image.ImageName, + Status: i.Status, + }, + ]), + ), + ); + }); - const events = new EventSource(`${base}/notifications?token=${access}`); + let events: EventSource | undefined; - events.addEventListener("data", dataEventListener); + createEffect(() => { + const token = accessToken(); + if (token) { + events = new EventSource(`${base}/notifications?token=${token}`); + events.addEventListener("data", dataEventListener); + events.onerror = (e) => { + console.error(e); + }; + } + }); - events.onerror = (e) => { - console.error(e); - }; + onCleanup(() => { + if (events) { + events.removeEventListener("data", dataEventListener); + events.close(); + } + }); - onCleanup(() => { - events.removeEventListener("data", dataEventListener); - events.close(); - }); - - return { - state, - }; + return { + state, + }; }; export const NotificationsContext = - createContext>(); + createContext>(); export const useNotifications = () => { - const notifications = useContext(NotificationsContext); - if (notifications == null) { - throw new Error("Cannot use this hook with an unmounted notifications"); - } + const notifications = useContext(NotificationsContext); + if (notifications == null) { + throw new Error("Cannot use this hook with an unmounted notifications"); + } - return notifications; + return notifications; }; export const WithNotifications: Component = (props) => { - const { onRefetchImages } = useSearchImageContext(); - const notifications = Notifications(onRefetchImages); + const { onRefetchImages } = useSearchImageContext(); + const notifications = Notifications(onRefetchImages); - return ( - - {props.children} - - ); + return ( + + {props.children} + + ); }; diff --git a/frontend/src/network/index.ts b/frontend/src/network/index.ts index a683514..3b56068 100644 --- a/frontend/src/network/index.ts +++ b/frontend/src/network/index.ts @@ -38,11 +38,7 @@ const refreshTokenValidator = strictObject({ access: string(), }) -const getBaseAuthorizedRequest = async ({ - path, - body, - method, -}: BaseRequestParams): Promise => { +export const getAccessToken = async (): Promise => { let accessToken = localStorage.getItem("access")?.toString(); const refreshToken = localStorage.getItem("refresh")?.toString(); @@ -65,6 +61,16 @@ const getBaseAuthorizedRequest = async ({ accessToken = access } + return accessToken! +} + +const getBaseAuthorizedRequest = async ({ + path, + body, + method, +}: BaseRequestParams): Promise => { + const accessToken = await getAccessToken(); + return new Request(`${base}/${path}`, { headers: { Authorization: `Bearer ${accessToken}`, diff --git a/frontend/src/pages/list/index.tsx b/frontend/src/pages/list/index.tsx index 04bcc32..da510c0 100644 --- a/frontend/src/pages/list/index.tsx +++ b/frontend/src/pages/list/index.tsx @@ -1,7 +1,7 @@ import { useSearchImageContext } from "@contexts/SearchImageContext"; import { useParams } from "@solidjs/router"; -import { Component, For, Show, createSignal } from "solid-js"; -import { base } from "../../network"; +import { Component, For, Show, createResource, createSignal } from "solid-js"; +import { base, getAccessToken } from "../../network"; import { Dialog } from "@kobalte/core"; const DeleteButton: Component<{ onDelete: () => void }> = (props) => { @@ -52,11 +52,7 @@ export const List: Component = () => { const { lists, onDeleteImageFromStack } = useSearchImageContext(); - // TODO: make sure this is up to date. Put it behind a resource. - const accessToken = localStorage.getItem("access"); - if (accessToken == null) { - return <>Ermm... Access token is not set :( - } + const [] = createResource(getAccessToken); const list = () => lists().find((l) => l.ID === listId);