diff --git a/frontend/src/components/CompatSession.tsx b/frontend/src/components/CompatSession.tsx index 5c36a245d..be7aa1f8f 100644 --- a/frontend/src/components/CompatSession.tsx +++ b/frontend/src/components/CompatSession.tsx @@ -18,6 +18,7 @@ import { atomFamily } from "jotai/utils"; import { atomWithMutation } from "jotai-urql"; import { useTransition } from "react"; +import { Link } from "../Router"; import { FragmentType, graphql, useFragment } from "../gql"; import { Session } from "./Session"; @@ -47,7 +48,7 @@ const END_SESSION_MUTATION = graphql(/* GraphQL */ ` } `); -const endCompatSessionFamily = atomFamily((id: string) => { +export const endCompatSessionFamily = atomFamily((id: string) => { const endCompatSession = atomWithMutation(END_SESSION_MUTATION); // A proxy atom which pre-sets the id variable in the mutation @@ -59,7 +60,7 @@ const endCompatSessionFamily = atomFamily((id: string) => { return endCompatSessionAtom; }); -const simplifyUrl = (url: string): string => { +export const simplifyUrl = (url: string): string => { let parsed; try { parsed = new URL(url); @@ -93,6 +94,10 @@ const CompatSession: React.FC<{ }); }; + const sessionName = ( + {data.deviceId} + ); + const clientName = data.ssoLogin?.redirectUri ? simplifyUrl(data.ssoLogin.redirectUri) : undefined; @@ -100,7 +105,7 @@ const CompatSession: React.FC<{ return ( { +export const endSessionFamily = atomFamily((id: string) => { const endSession = atomWithMutation(END_SESSION_MUTATION); // A proxy atom which pre-sets the id variable in the mutation @@ -96,12 +97,16 @@ const OAuth2Session: React.FC = ({ session }) => { }); }; - const sessionName = getDeviceIdFromScope(data.scope); + const deviceId = getDeviceIdFromScope(data.scope); + + const name = deviceId && ( + {deviceId} + ); return ( > = ( export type SessionProps = { id: string; - name?: string; + name?: string | ReactNode; createdAt: string; finishedAt?: string; clientName?: string; diff --git a/frontend/src/components/SessionDetail/CompatSessionDetail.tsx b/frontend/src/components/SessionDetail/CompatSessionDetail.tsx new file mode 100644 index 000000000..40a43a0b0 --- /dev/null +++ b/frontend/src/components/SessionDetail/CompatSessionDetail.tsx @@ -0,0 +1,96 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { H3, Button } from "@vector-im/compound-web"; +import { useSetAtom } from "jotai"; +import { useTransition } from "react"; + +import { FragmentType, useFragment } from "../../gql"; +import BlockList from "../BlockList/BlockList"; +import { + COMPAT_SESSION_FRAGMENT, + endCompatSessionFamily, + simplifyUrl, +} from "../CompatSession"; +import DateTime from "../DateTime"; + +import SessionDetails from "./SessionDetails"; + +type Props = { + session: FragmentType; +}; + +const CompatSessionDetail: React.FC = ({ session }) => { + const [pending, startTransition] = useTransition(); + const data = useFragment(COMPAT_SESSION_FRAGMENT, session); + const endSession = useSetAtom(endCompatSessionFamily(data.id)); + + // @TODO(kerrya) make this wait for session refresh properly + // https://github.com/matrix-org/matrix-authentication-service/issues/1533 + const onSessionEnd = (): void => { + startTransition(() => { + endSession(); + }); + }; + + const finishedAt = data.finishedAt + ? [{ label: "Finished", value: }] + : []; + const sessionDetails = [ + { label: "ID", value: {data.id} }, + { label: "Device ID", value: {data.deviceId} }, + { label: "Signed in", value: }, + ...finishedAt, + ]; + + const clientName = data.ssoLogin?.redirectUri + ? simplifyUrl(data.ssoLogin.redirectUri) + : undefined; + + const clientDetails = [ + { label: "Name", value: clientName }, + { + label: "Uri", + value: ( + + {data.ssoLogin?.redirectUri} + + ), + }, + ]; + + return ( +
+ +

{data.deviceId || data.id}

+ + + {!data.finishedAt && ( + + )} +
+
+ ); +}; + +export default CompatSessionDetail; diff --git a/frontend/src/components/SessionDetail/OAuth2SessionDetail.tsx b/frontend/src/components/SessionDetail/OAuth2SessionDetail.tsx new file mode 100644 index 000000000..8a34cd9da --- /dev/null +++ b/frontend/src/components/SessionDetail/OAuth2SessionDetail.tsx @@ -0,0 +1,113 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { H3, Button } from "@vector-im/compound-web"; +import { useSetAtom } from "jotai"; +import { useTransition } from "react"; + +import { FragmentType, useFragment } from "../../gql"; +import { getDeviceIdFromScope } from "../../utils/deviceIdFromScope"; +import BlockList from "../BlockList/BlockList"; +import DateTime from "../DateTime"; +import { + OAUTH2_SESSION_FRAGMENT, + Oauth2SessionType, + endSessionFamily, +} from "../OAuth2Session"; + +import SessionDetails from "./SessionDetails"; + +type Props = { + session: FragmentType; +}; + +const OAuth2SessionDetail: React.FC = ({ session }) => { + const [pending, startTransition] = useTransition(); + const data = useFragment( + OAUTH2_SESSION_FRAGMENT, + session, + ) as Oauth2SessionType; + const endSession = useSetAtom(endSessionFamily(data.id)); + + // @TODO(kerrya) make this wait for session refresh properly + // https://github.com/matrix-org/matrix-authentication-service/issues/1533 + const onSessionEnd = (): void => { + startTransition(() => { + endSession(); + }); + }; + + const deviceId = getDeviceIdFromScope(data.scope); + + const scopes = data.scope.split(" "); + + const finishedAt = data.finishedAt + ? [{ label: "Finished", value: }] + : []; + const sessionDetails = [ + { label: "ID", value: {data.id} }, + { label: "Device ID", value: {deviceId} }, + { label: "Signed in", value: }, + ...finishedAt, + { + label: "Scopes", + value: ( + <> + {scopes.map((scope) => ( +

+ {scope} +

+ ))} + + ), + }, + ]; + + const clientDetails = [ + { label: "Name", value: data.client.clientName }, + { label: "ID", value: {data.client.clientId} }, + { + label: "Uri", + value: ( + + {data.client.clientUri} + + ), + }, + ]; + + return ( +
+ +

{deviceId || data.id}

+ + + {!data.finishedAt && ( + + )} +
+
+ ); +}; + +export default OAuth2SessionDetail; diff --git a/frontend/src/components/SessionDetail/SessionDetail.tsx b/frontend/src/components/SessionDetail/SessionDetail.tsx index 474ac5a82..71323181b 100644 --- a/frontend/src/components/SessionDetail/SessionDetail.tsx +++ b/frontend/src/components/SessionDetail/SessionDetail.tsx @@ -20,8 +20,9 @@ import { useMemo } from "react"; import { Link } from "../../Router"; import { graphql } from "../../gql/gql"; -import CompatSession from "../CompatSession"; -import OAuth2Session from "../OAuth2Session"; + +import CompatSessionDetail from "./CompatSessionDetail"; +import OAuth2SessionDetail from "./OAuth2SessionDetail"; const QUERY = graphql(/* GraphQL */ ` query SessionQuery($userId: ID!, $deviceId: String!) { @@ -70,9 +71,9 @@ const SessionDetail: React.FC<{ const sessionType = session.__typename; if (sessionType === "Oauth2Session") { - return ; + return ; } else { - return ; + return ; } }; diff --git a/frontend/src/components/SessionDetail/SessionDetails.module.css b/frontend/src/components/SessionDetail/SessionDetails.module.css new file mode 100644 index 000000000..730be4c59 --- /dev/null +++ b/frontend/src/components/SessionDetail/SessionDetails.module.css @@ -0,0 +1,36 @@ +/* Copyright 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.list { + display: flex; + flex-direction: column; + margin-top: var(--cpd-space-1x); + gap: var(--cpd-space-1x); +} + +.detail-row { + display: flex; + flex-direction: row; + gap: var(--cpd-space-4x); +} + +.detail-label { + flex: 0 0 20%; + color: var(--cpd-color-text-secondary); +} + +.detail-value { + overflow-wrap: anywhere; +} diff --git a/frontend/src/components/SessionDetail/SessionDetails.tsx b/frontend/src/components/SessionDetail/SessionDetails.tsx new file mode 100644 index 000000000..74eddaaa8 --- /dev/null +++ b/frontend/src/components/SessionDetail/SessionDetails.tsx @@ -0,0 +1,52 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { H6, Body } from "@vector-im/compound-web"; +import { ReactNode } from "react"; + +import Block from "../Block/Block"; + +import styles from "./SessionDetails.module.css"; + +type Detail = { label: string; value: string | ReactNode }; +type Props = { + title: string; + details: Detail[]; +}; + +const DetailRow: React.FC = ({ label, value }) => ( +
  • + + {label} + + + {value} + +
  • +); + +const SessionDetails: React.FC = ({ title, details }) => { + return ( + +
    {title}
    +
      + {details.map(({ label, value }) => ( + + ))} +
    +
    + ); +}; + +export default SessionDetails; diff --git a/frontend/src/components/__snapshots__/CompatSession.test.tsx.snap b/frontend/src/components/__snapshots__/CompatSession.test.tsx.snap index 8e0b8353f..20f2f6dec 100644 --- a/frontend/src/components/__snapshots__/CompatSession.test.tsx.snap +++ b/frontend/src/components/__snapshots__/CompatSession.test.tsx.snap @@ -8,7 +8,13 @@ exports[` > renders a finished session 1`] = ` className="_font-body-md-semibold_1g2sj_69 _sessionName_634806" title="session-id" > - abcd1234 + + abcd1234 +

    > renders an active session 1`] = ` className="_font-body-md-semibold_1g2sj_69 _sessionName_634806" title="session-id" > - abcd1234 + + abcd1234 +

    > renders a finished session 1`] = ` className="_font-body-md-semibold_1g2sj_69 _sessionName_634806" title="session-id" > - abcd1234 + + abcd1234 +

    > renders an active session 1`] = ` className="_font-body-md-semibold_1g2sj_69 _sessionName_634806" title="session-id" > - abcd1234 + + abcd1234 +