diff --git a/frontend/src/components/BrowserSession.tsx b/frontend/src/components/BrowserSession.tsx index 75a5b792..846dde40 100644 --- a/frontend/src/components/BrowserSession.tsx +++ b/frontend/src/components/BrowserSession.tsx @@ -21,7 +21,6 @@ import { parseUserAgent, sessionNameFromDeviceInformation, } from "../utils/parseUserAgent"; -import { useCurrentBrowserSessionId } from "../utils/session/useCurrentBrowserSessionId"; import EndSessionButton from "./Session/EndSessionButton"; import Session from "./Session/Session"; @@ -71,12 +70,11 @@ export const useEndBrowserSession = ( type Props = { session: FragmentType; + isCurrent: boolean; }; -const BrowserSession: React.FC = ({ session }) => { - const currentBrowserSessionId = useCurrentBrowserSessionId(); +const BrowserSession: React.FC = ({ session, isCurrent }) => { const data = useFragment(FRAGMENT, session); - const isCurrent = data.id === currentBrowserSessionId; const onSessionEnd = useEndBrowserSession(data.id, isCurrent); diff --git a/frontend/src/components/SessionDetail/BrowserSessionDetail.tsx b/frontend/src/components/SessionDetail/BrowserSessionDetail.tsx index b364ade4..18d34cc5 100644 --- a/frontend/src/components/SessionDetail/BrowserSessionDetail.tsx +++ b/frontend/src/components/SessionDetail/BrowserSessionDetail.tsx @@ -21,7 +21,6 @@ import { parseUserAgent, sessionNameFromDeviceInformation, } from "../../utils/parseUserAgent"; -import { useCurrentBrowserSessionId } from "../../utils/session/useCurrentBrowserSessionId"; import BlockList from "../BlockList/BlockList"; import { useEndBrowserSession } from "../BrowserSession"; import DateTime from "../DateTime"; @@ -53,14 +52,13 @@ const FRAGMENT = graphql(/* GraphQL */ ` type Props = { session: FragmentType; + isCurrent: boolean; }; -const BrowserSessionDetail: React.FC = ({ session }) => { +const BrowserSessionDetail: React.FC = ({ session, isCurrent }) => { const data = useFragment(FRAGMENT, session); - const currentBrowserSessionId = useCurrentBrowserSessionId(); const { t } = useTranslation(); - const isCurrent = currentBrowserSessionId === data.id; const onSessionEnd = useEndBrowserSession(data.id, isCurrent); const deviceInformation = parseUserAgent(data.userAgent || undefined); diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 54f637ef..014df6b9 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -63,9 +63,9 @@ const documents = { types.ResendVerificationEmailDocument, "\n query UserProfileQuery {\n viewer {\n __typename\n ... on User {\n id\n ...UserName_user\n ...UserEmailList_user\n }\n }\n }\n": types.UserProfileQueryDocument, - "\n query SessionDetailQuery($id: ID!) {\n node(id: $id) {\n __typename\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": + "\n query SessionDetailQuery($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n": types.SessionDetailQueryDocument, - "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n": + "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n": types.BrowserSessionListDocument, "\n query SessionsOverviewQuery {\n viewer {\n __typename\n\n ... on User {\n id\n ...BrowserSessionsOverview_user\n }\n }\n }\n": types.SessionsOverviewQueryDocument, @@ -255,14 +255,14 @@ export function graphql( * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query SessionDetailQuery($id: ID!) {\n node(id: $id) {\n __typename\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n", -): (typeof documents)["\n query SessionDetailQuery($id: ID!) {\n node(id: $id) {\n __typename\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n"]; + source: "\n query SessionDetailQuery($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n", +): (typeof documents)["\n query SessionDetailQuery($id: ID!) {\n viewerSession {\n ... on Node {\n id\n }\n }\n\n node(id: $id) {\n __typename\n id\n ...CompatSession_detail\n ...OAuth2Session_detail\n ...BrowserSession_detail\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql( - source: "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n", -): (typeof documents)["\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewer {\n __typename\n ... on User {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n"]; + source: "\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n", +): (typeof documents)["\n query BrowserSessionList(\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n viewerSession {\n __typename\n ... on BrowserSession {\n id\n\n user {\n id\n\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n state: ACTIVE\n ) {\n totalCount\n\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 872de9c4..71b231b5 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -1510,30 +1510,34 @@ export type SessionDetailQueryQueryVariables = Exact<{ export type SessionDetailQueryQuery = { __typename?: "Query"; + viewerSession: + | { __typename?: "Anonymous"; id: string } + | { __typename?: "BrowserSession"; id: string } + | { __typename?: "Oauth2Session"; id: string }; node?: - | { __typename: "Anonymous" } - | { __typename: "Authentication" } - | ({ __typename: "BrowserSession" } & { + | { __typename: "Anonymous"; id: string } + | { __typename: "Authentication"; id: string } + | ({ __typename: "BrowserSession"; id: string } & { " $fragmentRefs"?: { BrowserSession_DetailFragment: BrowserSession_DetailFragment; }; }) - | ({ __typename: "CompatSession" } & { + | ({ __typename: "CompatSession"; id: string } & { " $fragmentRefs"?: { CompatSession_DetailFragment: CompatSession_DetailFragment; }; }) - | { __typename: "CompatSsoLogin" } - | { __typename: "Oauth2Client" } - | ({ __typename: "Oauth2Session" } & { + | { __typename: "CompatSsoLogin"; id: string } + | { __typename: "Oauth2Client"; id: string } + | ({ __typename: "Oauth2Session"; id: string } & { " $fragmentRefs"?: { OAuth2Session_DetailFragment: OAuth2Session_DetailFragment; }; }) - | { __typename: "UpstreamOAuth2Link" } - | { __typename: "UpstreamOAuth2Provider" } - | { __typename: "User" } - | { __typename: "UserEmail" } + | { __typename: "UpstreamOAuth2Link"; id: string } + | { __typename: "UpstreamOAuth2Provider"; id: string } + | { __typename: "User"; id: string } + | { __typename: "UserEmail"; id: string } | null; }; @@ -1546,32 +1550,37 @@ export type BrowserSessionListQueryVariables = Exact<{ export type BrowserSessionListQuery = { __typename?: "Query"; - viewer: + viewerSession: | { __typename: "Anonymous" } | { - __typename: "User"; + __typename: "BrowserSession"; id: string; - browserSessions: { - __typename?: "BrowserSessionConnection"; - totalCount: number; - edges: Array<{ - __typename?: "BrowserSessionEdge"; - cursor: string; - node: { __typename?: "BrowserSession"; id: string } & { - " $fragmentRefs"?: { - BrowserSession_SessionFragment: BrowserSession_SessionFragment; + user: { + __typename?: "User"; + id: string; + browserSessions: { + __typename?: "BrowserSessionConnection"; + totalCount: number; + edges: Array<{ + __typename?: "BrowserSessionEdge"; + cursor: string; + node: { __typename?: "BrowserSession"; id: string } & { + " $fragmentRefs"?: { + BrowserSession_SessionFragment: BrowserSession_SessionFragment; + }; }; + }>; + pageInfo: { + __typename?: "PageInfo"; + hasNextPage: boolean; + hasPreviousPage: boolean; + startCursor?: string | null; + endCursor?: string | null; }; - }>; - pageInfo: { - __typename?: "PageInfo"; - hasNextPage: boolean; - hasPreviousPage: boolean; - startCursor?: string | null; - endCursor?: string | null; }; }; - }; + } + | { __typename: "Oauth2Session" }; }; export type SessionsOverviewQueryQueryVariables = Exact<{ @@ -3417,6 +3426,28 @@ export const SessionDetailQueryDocument = { selectionSet: { kind: "SelectionSet", selections: [ + { + kind: "Field", + name: { kind: "Name", value: "viewerSession" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "InlineFragment", + typeCondition: { + kind: "NamedType", + name: { kind: "Name", value: "Node" }, + }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + ], + }, + }, + ], + }, + }, { kind: "Field", name: { kind: "Name", value: "node" }, @@ -3434,6 +3465,7 @@ export const SessionDetailQueryDocument = { kind: "SelectionSet", selections: [ { kind: "Field", name: { kind: "Name", value: "__typename" } }, + { kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "FragmentSpread", name: { kind: "Name", value: "CompatSession_detail" }, @@ -3604,7 +3636,7 @@ export const BrowserSessionListDocument = { selections: [ { kind: "Field", - name: { kind: "Name", value: "viewer" }, + name: { kind: "Name", value: "viewerSession" }, selectionSet: { kind: "SelectionSet", selections: [ @@ -3613,7 +3645,7 @@ export const BrowserSessionListDocument = { kind: "InlineFragment", typeCondition: { kind: "NamedType", - name: { kind: "Name", value: "User" }, + name: { kind: "Name", value: "BrowserSession" }, }, selectionSet: { kind: "SelectionSet", @@ -3621,117 +3653,140 @@ export const BrowserSessionListDocument = { { kind: "Field", name: { kind: "Name", value: "id" } }, { kind: "Field", - name: { kind: "Name", value: "browserSessions" }, - arguments: [ - { - kind: "Argument", - name: { kind: "Name", value: "first" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "first" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "after" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "after" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "last" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "last" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "before" }, - value: { - kind: "Variable", - name: { kind: "Name", value: "before" }, - }, - }, - { - kind: "Argument", - name: { kind: "Name", value: "state" }, - value: { kind: "EnumValue", value: "ACTIVE" }, - }, - ], + name: { kind: "Name", value: "user" }, selectionSet: { kind: "SelectionSet", selections: [ { kind: "Field", - name: { kind: "Name", value: "totalCount" }, + name: { kind: "Name", value: "id" }, }, { kind: "Field", - name: { kind: "Name", value: "edges" }, + name: { kind: "Name", value: "browserSessions" }, + arguments: [ + { + kind: "Argument", + name: { kind: "Name", value: "first" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "first" }, + }, + }, + { + kind: "Argument", + name: { kind: "Name", value: "after" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "after" }, + }, + }, + { + kind: "Argument", + name: { kind: "Name", value: "last" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "last" }, + }, + }, + { + kind: "Argument", + name: { kind: "Name", value: "before" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "before" }, + }, + }, + { + kind: "Argument", + name: { kind: "Name", value: "state" }, + value: { kind: "EnumValue", value: "ACTIVE" }, + }, + ], selectionSet: { kind: "SelectionSet", selections: [ { kind: "Field", - name: { kind: "Name", value: "cursor" }, + name: { kind: "Name", value: "totalCount" }, }, { kind: "Field", - name: { kind: "Name", value: "node" }, + name: { kind: "Name", value: "edges" }, selectionSet: { kind: "SelectionSet", selections: [ { kind: "Field", - name: { kind: "Name", value: "id" }, - }, - { - kind: "FragmentSpread", name: { kind: "Name", - value: "BrowserSession_session", + value: "cursor", + }, + }, + { + kind: "Field", + name: { kind: "Name", value: "node" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { + kind: "Name", + value: "id", + }, + }, + { + kind: "FragmentSpread", + name: { + kind: "Name", + value: + "BrowserSession_session", + }, + }, + ], }, }, ], }, }, - ], - }, - }, - { - kind: "Field", - name: { kind: "Name", value: "pageInfo" }, - selectionSet: { - kind: "SelectionSet", - selections: [ { kind: "Field", - name: { - kind: "Name", - value: "hasNextPage", + name: { kind: "Name", value: "pageInfo" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { + kind: "Name", + value: "hasNextPage", + }, + }, + { + kind: "Field", + name: { + kind: "Name", + value: "hasPreviousPage", + }, + }, + { + kind: "Field", + name: { + kind: "Name", + value: "startCursor", + }, + }, + { + kind: "Field", + name: { + kind: "Name", + value: "endCursor", + }, + }, + ], }, }, - { - kind: "Field", - name: { - kind: "Name", - value: "hasPreviousPage", - }, - }, - { - kind: "Field", - name: { - kind: "Name", - value: "startCursor", - }, - }, - { - kind: "Field", - name: { kind: "Name", value: "endCursor" }, - }, ], }, }, diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 0b9d7c05..324ba393 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -33,6 +33,7 @@ const router = createRouter({ routeTree, basepath: config.root, defaultErrorComponent: GenericError, + defaultPreload: "intent", context: { client }, }); diff --git a/frontend/src/routes/_account.sessions.$id.tsx b/frontend/src/routes/_account.sessions.$id.tsx index 103f3dd9..c011ad35 100644 --- a/frontend/src/routes/_account.sessions.$id.tsx +++ b/frontend/src/routes/_account.sessions.$id.tsx @@ -40,8 +40,15 @@ export const Route = createFileRoute("/_account/sessions/$id")({ const QUERY = graphql(/* GraphQL */ ` query SessionDetailQuery($id: ID!) { + viewerSession { + ... on Node { + id + } + } + node(id: $id) { __typename + id ...CompatSession_detail ...OAuth2Session_detail ...BrowserSession_detail @@ -72,6 +79,7 @@ function SessionDetail(): React.ReactElement { if (result.error) throw result.error; const node = result.data?.node; if (!node) throw notFound(); + const currentSessionId = result.data?.viewerSession?.id; switch (node.__typename) { case "CompatSession": @@ -79,7 +87,12 @@ function SessionDetail(): React.ReactElement { case "Oauth2Session": return ; case "BrowserSession": - return ; + return ( + + ); default: throw new Error("Unknown session type"); } diff --git a/frontend/src/routes/_account.sessions.browsers.tsx b/frontend/src/routes/_account.sessions.browsers.tsx index e6be7f0f..4698c54d 100644 --- a/frontend/src/routes/_account.sessions.browsers.tsx +++ b/frontend/src/routes/_account.sessions.browsers.tsx @@ -37,32 +37,37 @@ const QUERY = graphql(/* GraphQL */ ` $last: Int $before: String ) { - viewer { + viewerSession { __typename - ... on User { + ... on BrowserSession { id - browserSessions( - first: $first - after: $after - last: $last - before: $before - state: ACTIVE - ) { - totalCount - edges { - cursor - node { - id - ...BrowserSession_session + user { + id + + browserSessions( + first: $first + after: $after + last: $last + before: $before + state: ACTIVE + ) { + totalCount + + edges { + cursor + node { + id + ...BrowserSession_session + } } - } - pageInfo { - hasNextPage - hasPreviousPage - startCursor - endCursor + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } } } } @@ -84,7 +89,8 @@ export const Route = createFileRoute("/_account/sessions/browsers")({ fetchOptions: { signal }, }); if (result.error) throw result.error; - if (result.data?.viewer?.__typename !== "User") throw notFound(); + if (result.data?.viewerSession?.__typename !== "BrowserSession") + throw notFound(); }, component: BrowserSessions, @@ -95,26 +101,30 @@ function BrowserSessions(): React.ReactElement { const pagination = Route.useLoaderDeps(); const [list] = useQuery({ query: QUERY, variables: pagination }); if (list.error) throw list.error; - const browserSessions = - list.data?.viewer.__typename === "User" - ? list.data.viewer.browserSessions + const currentSession = + list.data?.viewerSession.__typename === "BrowserSession" + ? list.data.viewerSession : null; - if (browserSessions === null) throw notFound(); + if (currentSession === null) throw notFound(); const [backwardPage, forwardPage] = usePages( pagination, - browserSessions.pageInfo, + currentSession.user.browserSessions.pageInfo, PAGE_SIZE, ); // We reverse the list as we are paginating backwards - const edges = [...browserSessions.edges].reverse(); + const edges = [...currentSession.user.browserSessions.edges].reverse(); return (
{t("frontend.browser_sessions_overview.heading")}
{edges.map((n) => ( - + ))}