From c7381ed5c6a284279e4f36a6f2c07eebea5af666 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sun, 23 Oct 2022 09:30:46 +0200
Subject: [PATCH] added detailed reports route
---
package.json | 2 +
src/App.tsx | 6 ++
src/apis/get-report.ts | 13 +++
src/apis/helpers/parse-decrypted-report.ts | 9 ++
src/apis/index.ts | 2 +
.../DecryptReport.tsx | 2 +-
src/components/index.ts | 2 +
.../ProxiedImagesListItem.tsx | 99 +++++++++++++++++++
.../SinglePixelImageTrackersListItem.tsx | 77 +++++++++++++++
.../ReportsRoute/ReportInformationItem.tsx | 29 ++++++
src/routes/ReportDetailRoute.tsx | 98 ++++++++++++++++++
src/routes/ReportsRoute.tsx | 20 ++--
src/server-types.ts | 4 +
13 files changed, 349 insertions(+), 14 deletions(-)
create mode 100644 src/apis/get-report.ts
rename src/{route-widgets/SettingsRoute => components}/DecryptReport.tsx (92%)
create mode 100644 src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx
create mode 100644 src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx
create mode 100644 src/route-widgets/ReportsRoute/ReportInformationItem.tsx
create mode 100644 src/routes/ReportDetailRoute.tsx
diff --git a/package.json b/package.json
index f2ffac5..7215662 100755
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"crypto-js": "^4.1.1",
"date-fns": "^2.29.3",
"formik": "^2.2.9",
+ "group-array": "^1.0.0",
"in-seconds": "^1.2.0",
"openpgp": "^5.5.0",
"react": "^18.2.0",
@@ -37,6 +38,7 @@
"devDependencies": {
"@types/crypto-js": "^4.1.1",
"@types/date-fns": "^2.6.0",
+ "@types/group-array": "^1.0.1",
"@types/openpgp": "^4.4.18",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
diff --git a/src/App.tsx b/src/App.tsx
index a407e1f..0483b09 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -14,6 +14,7 @@ import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
import EnterDecryptionPassword from "~/routes/EnterDecryptionPassword"
import LoginRoute from "~/routes/LoginRoute"
+import ReportDetailRoute from "~/routes/ReportDetailRoute"
import ReportsRoute from "~/routes/ReportsRoute"
import RootRoute from "~/routes/Root"
import SettingsRoute from "~/routes/SettingsRoute"
@@ -67,6 +68,11 @@ const router = createBrowserRouter([
path: "/reports",
element: ,
},
+ {
+ loader: getServerSettings,
+ path: "/reports/:id",
+ element: ,
+ },
{
path: "/enter-password",
element: ,
diff --git a/src/apis/get-report.ts b/src/apis/get-report.ts
new file mode 100644
index 0000000..9112c3b
--- /dev/null
+++ b/src/apis/get-report.ts
@@ -0,0 +1,13 @@
+import {client} from "~/constants/axios-client"
+import {Report} from "~/server-types"
+
+export default async function getReport(id: string): Promise {
+ const {data} = await client.get(
+ `${import.meta.env.VITE_SERVER_BASE_URL}/report/${id}`,
+ {
+ withCredentials: true,
+ },
+ )
+
+ return data
+}
diff --git a/src/apis/helpers/parse-decrypted-report.ts b/src/apis/helpers/parse-decrypted-report.ts
index 501cb99..3c1abb5 100644
--- a/src/apis/helpers/parse-decrypted-report.ts
+++ b/src/apis/helpers/parse-decrypted-report.ts
@@ -11,6 +11,15 @@ export default function parseDecryptedReport(
...report.messageDetails.meta,
createdAt: new Date(report.messageDetails.meta.createdAt),
},
+ content: {
+ ...report.messageDetails.content,
+ proxiedImages: report.messageDetails.content.proxiedImages.map(
+ image => ({
+ ...image,
+ createdAt: new Date(image.createdAt),
+ }),
+ ),
+ },
},
}
}
diff --git a/src/apis/index.ts b/src/apis/index.ts
index 1f94589..140337f 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -28,3 +28,5 @@ export * from "./update-preferences"
export {default as updatePreferences} from "./update-preferences"
export * from "./get-reports"
export {default as getReports} from "./get-reports"
+export * from "./get-report"
+export {default as getReport} from "./get-report"
diff --git a/src/route-widgets/SettingsRoute/DecryptReport.tsx b/src/components/DecryptReport.tsx
similarity index 92%
rename from src/route-widgets/SettingsRoute/DecryptReport.tsx
rename to src/components/DecryptReport.tsx
index 5afe06f..7980d97 100644
--- a/src/route-widgets/SettingsRoute/DecryptReport.tsx
+++ b/src/components/DecryptReport.tsx
@@ -19,7 +19,7 @@ export default function DecryptReport({
const {value} = useAsync(async () => {
const message = await _decryptUsingPrivateKey(encryptedContent)
- const content = camelcaseKeys(JSON.parse(message))
+ const content = camelcaseKeys(JSON.parse(message), {deep: true})
return parseDecryptedReport(content)
}, [encryptedContent])
diff --git a/src/components/index.ts b/src/components/index.ts
index 351baf2..e10ea74 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -18,3 +18,5 @@ export * from "./SuccessSnack"
export {default as SuccessSnack} from "./SuccessSnack"
export * from "./ErrorLoadingDataMessage"
export {default as ErrorLoadingDataMessage} from "./ErrorLoadingDataMessage"
+export * from "./DecryptReport"
+export {default as DecryptReport} from "./DecryptReport"
diff --git a/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx b/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx
new file mode 100644
index 0000000..399e6b1
--- /dev/null
+++ b/src/route-widgets/ReportDetailRoute/ProxiedImagesListItem.tsx
@@ -0,0 +1,99 @@
+import {BsImage} from "react-icons/bs"
+import {ReactElement, useState} from "react"
+import {MdLocationOn} from "react-icons/md"
+import {useLoaderData} from "react-router-dom"
+import addHours from "date-fns/addHours"
+import isBefore from "date-fns/isBefore"
+
+import {
+ Box,
+ Collapse,
+ Grid,
+ List,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ useTheme,
+} from "@mui/material"
+
+import {DecryptedReportContent, ServerSettings} from "~/server-types"
+
+export interface ProxiedImagesListItemProps {
+ images: DecryptedReportContent["messageDetails"]["content"]["proxiedImages"]
+}
+
+export default function ProxiedImagesListItem({
+ images,
+}: ProxiedImagesListItemProps): ReactElement {
+ const serverSettings = useLoaderData() as ServerSettings
+ const theme = useTheme()
+
+ const [showProxiedImages, setShowProxiedImages] = useState(false)
+
+ return (
+ <>
+ {
+ if (images.length > 0) {
+ setShowProxiedImages(value => !value)
+ }
+ }}
+ >
+
+
+
+ Proxying {images.length} images
+
+
+
+
+ {images.map(image => (
+
+
+
+
+
+
+
+ {(() => {
+ if (
+ isBefore(
+ new Date(),
+ addHours(
+ image.createdAt,
+ serverSettings.imageProxyLifeTime,
+ ),
+ )
+ ) {
+ return "Stored on Server."
+ } else {
+ return "Proxying through Server."
+ }
+ })()}
+
+
+ >
+ }
+ />
+
+ ))}
+
+
+
+ >
+ )
+}
diff --git a/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx b/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx
new file mode 100644
index 0000000..c94b04d
--- /dev/null
+++ b/src/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem.tsx
@@ -0,0 +1,77 @@
+import {ReactElement, useState} from "react"
+
+import {
+ Box,
+ Collapse,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ Typography,
+ useTheme,
+} from "@mui/material"
+
+import {DecryptedReportContent} from "~/server-types"
+import {BsShieldShaded} from "react-icons/bs"
+
+export interface SinglePixelImageTrackersListItemProps {
+ images: DecryptedReportContent["messageDetails"]["content"]["singlePixelImages"]
+}
+
+export default function SinglePixelImageTrackersListItem({
+ images,
+}: SinglePixelImageTrackersListItemProps): ReactElement {
+ const theme = useTheme()
+
+ const [showImageTrackers, setShowImageTrackers] = useState(false)
+
+ const imagesPerTracker = images.reduce((acc, value) => {
+ acc[value.trackerName] = [...(acc[value.trackerName] || []), value]
+
+ return acc
+ }, {} as Record>)
+
+ return (
+ <>
+ {
+ if (images.length > 0) {
+ setShowImageTrackers(value => !value)
+ }
+ }}
+ >
+
+
+
+
+ Removed {images.length} image trackers
+
+
+
+
+
+ {Object.entries(imagesPerTracker).map(
+ ([trackerName, images]) => (
+ <>
+
+ {trackerName}
+
+ {images.map(image => (
+
+ {image.source}
+
+ ))}
+ >
+ ),
+ )}
+
+
+
+ >
+ )
+}
diff --git a/src/route-widgets/ReportsRoute/ReportInformationItem.tsx b/src/route-widgets/ReportsRoute/ReportInformationItem.tsx
new file mode 100644
index 0000000..ca563f5
--- /dev/null
+++ b/src/route-widgets/ReportsRoute/ReportInformationItem.tsx
@@ -0,0 +1,29 @@
+import {ReactElement} from "react"
+import {useNavigate} from "react-router-dom"
+
+import {ListItemButton, ListItemText} from "@mui/material"
+
+import {DecryptedReportContent} from "~/server-types"
+
+export interface ReportInformationItemProps {
+ report: DecryptedReportContent
+}
+
+export default function ReportInformationItem({
+ report,
+}: ReportInformationItemProps): ReactElement {
+ const navigate = useNavigate()
+
+ return (
+ navigate(`/reports/${report.id}`)}>
+ {""}
+ )
+ }
+ secondary={`${report.messageDetails.meta.from} -> ${report.messageDetails.meta.to}`}
+ />
+
+ )
+}
diff --git a/src/routes/ReportDetailRoute.tsx b/src/routes/ReportDetailRoute.tsx
new file mode 100644
index 0000000..0d8a5c4
--- /dev/null
+++ b/src/routes/ReportDetailRoute.tsx
@@ -0,0 +1,98 @@
+import {useParams} from "react-router-dom"
+import {AxiosError} from "axios"
+import React, {ReactElement} from "react"
+
+import {useQuery} from "@tanstack/react-query"
+import {Box, Grid, List, Typography} from "@mui/material"
+
+import {Report} from "~/server-types"
+import {getReport} from "~/apis"
+import {DecryptReport} from "~/components"
+import ProxiedImagesListItem from "~/route-widgets/ReportDetailRoute/ProxiedImagesListItem"
+import QueryResult from "~/components/QueryResult"
+import SinglePixelImageTrackersListItem from "~/route-widgets/ReportDetailRoute/SinglePixelImageTrackersListItem"
+
+export default function ReportDetailRoute(): ReactElement {
+ const params = useParams()
+
+ const query = useQuery(["get_report", params.id], () =>
+ getReport(params.id as string),
+ )
+
+ return (
+ query={query}>
+ {encryptedReport => (
+
+ {report => (
+
+
+
+ Email Report
+
+
+
+
+ Email information
+
+
+
+ From
+
+
+ {report.messageDetails.meta.from}
+
+
+
+
+ To
+
+
+ {report.messageDetails.meta.to}
+
+
+
+
+ Subject
+
+
+ {report.messageDetails.content.subject}
+
+
+
+
+
+ Trackers
+
+
+
+
+
+
+
+ )}
+
+ )}
+
+ )
+}
diff --git a/src/routes/ReportsRoute.tsx b/src/routes/ReportsRoute.tsx
index 8211793..ddd57e9 100644
--- a/src/routes/ReportsRoute.tsx
+++ b/src/routes/ReportsRoute.tsx
@@ -2,13 +2,14 @@ import {ReactElement} from "react"
import {AxiosError} from "axios"
import {useQuery} from "@tanstack/react-query"
-import {List, ListItem, ListItemText} from "@mui/material"
+import {List} from "@mui/material"
import {PaginationResult, Report} from "~/server-types"
import {getReports} from "~/apis"
import {WithEncryptionRequired} from "~/hocs"
-import DecryptReport from "~/route-widgets/SettingsRoute/DecryptReport"
+import {DecryptReport} from "~/components"
import QueryResult from "~/components/QueryResult"
+import ReportInformationItem from "~/route-widgets/ReportsRoute/ReportInformationItem"
function ReportsRoute(): ReactElement {
const query = useQuery, AxiosError>(
@@ -26,17 +27,10 @@ function ReportsRoute(): ReactElement {
encryptedContent={report.encryptedContent}
>
{report => (
-
- {""}
- )
- }
- secondary={`${report.messageDetails.meta.from} -> ${report.messageDetails.meta.to}`}
- >
-
+
)}
))}
diff --git a/src/server-types.ts b/src/server-types.ts
index d475675..61c31e9 100644
--- a/src/server-types.ts
+++ b/src/server-types.ts
@@ -96,8 +96,10 @@ export interface Report {
export interface DecryptedReportContent {
version: "1.0"
+ id: string
messageDetails: {
meta: {
+ messageId: string
from: string
to: string
createdAt: Date
@@ -107,6 +109,8 @@ export interface DecryptedReportContent {
proxiedImages: Array<{
url: string
imageProxyId: string
+ createdAt: Date
+ serverUrl: string
}>
singlePixelImages: Array<{
source: string