mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-10-23 23:54:14 +00:00
DM: Make session UI closer to design (#1538)
* setup * new session tile style * update compound-web * use compound components * use deviceId from scope as session name * add basic test for Session * border color * use new Session in compat session * tidy * tidy * Revert "setup" This reverts commit ee86051dc4f6c6ca2f9d57ce39fa93396817a974. * disable "deprecate/import" enzyme rule from matrix-eslint that breaks lint * lint * correct copyright year * more lint * try to stabilise timezone for session tests * just stub DateTime in test * test deviceIdFromScope * tests * revert config change
This commit is contained in:
1 parent
ff1f33d94a
commit
a068e496f0
16 files changed
+742
-106
No files matched your search
@@ -53,6 +53,9 @@ module.exports = {
|
||||
alphabetize: { order: "asc" },
|
||||
},
|
||||
],
|
||||
// override enzyme deprecation from matrix-eslint
|
||||
// as it causes errors
|
||||
"deprecate/import": "off",
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
*/
|
||||
|
||||
.block {
|
||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
||||
color: var(--cpd-color-text-primary);
|
||||
padding: var(--cpd-space-4x);
|
||||
border-radius: var(--cpd-space-2x);
|
||||
padding: var(--cpd-space-5x) 0;
|
||||
border-bottom: 1px solid var(--cpd-color-border-interactive-secondary);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,5 @@
|
||||
.block-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-4x);
|
||||
align-content: flex-start;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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.
|
||||
|
||||
import { create } from "react-test-renderer";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { FragmentType } from "../gql/fragment-masking";
|
||||
|
||||
import CompatSession, { COMPAT_SESSION_FRAGMENT } from "./CompatSession";
|
||||
import DateTime from "./DateTime";
|
||||
|
||||
// Mock out datetime to avoid timezones/date formatting
|
||||
vi.mock("./DateTime", () => {
|
||||
const MockDateTime: typeof DateTime = ({ datetime }) => (
|
||||
<code>{datetime.toString()}</code>
|
||||
);
|
||||
return { default: MockDateTime };
|
||||
});
|
||||
|
||||
describe("<CompatSession />", () => {
|
||||
const session = {
|
||||
id: "session-id",
|
||||
deviceId: "abcd1234",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
ssoLogin: {
|
||||
id: "test-id",
|
||||
redirectUri: "https://element.io",
|
||||
},
|
||||
} as FragmentType<typeof COMPAT_SESSION_FRAGMENT>;
|
||||
|
||||
const finishedAt = "2023-06-29T03:35:19.451292+00:00";
|
||||
|
||||
it("renders an active session", () => {
|
||||
const component = create(<CompatSession session={session} />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished session", () => {
|
||||
const finishedSession = {
|
||||
...session,
|
||||
finishedAt,
|
||||
};
|
||||
const component = create(<CompatSession session={finishedSession} />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -20,10 +20,9 @@ import { useTransition } from "react";
|
||||
|
||||
import { FragmentType, graphql, useFragment } from "../gql";
|
||||
|
||||
import Block from "./Block";
|
||||
import DateTime from "./DateTime";
|
||||
import { Body, Bold, Code } from "./Typography";
|
||||
import { Session } from "./Session";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const LOGIN_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSession_sso_login on CompatSsoLogin {
|
||||
id
|
||||
@@ -31,7 +30,7 @@ const LOGIN_FRAGMENT = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const COMPAT_SESSION_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment CompatSession_session on CompatSession {
|
||||
id
|
||||
createdAt
|
||||
@@ -44,6 +43,17 @@ const FRAGMENT = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
type CompatSessionType = {
|
||||
id: string;
|
||||
deviceId: string;
|
||||
createdAt: string;
|
||||
finishedAt: string | null;
|
||||
ssoLogin: {
|
||||
id: string;
|
||||
redirectUri: string;
|
||||
};
|
||||
};
|
||||
|
||||
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation EndCompatSession($id: ID!) {
|
||||
endCompatSession(input: { compatSessionId: $id }) {
|
||||
@@ -69,10 +79,13 @@ const endCompatSessionFamily = atomFamily((id: string) => {
|
||||
});
|
||||
|
||||
const CompatSession: React.FC<{
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
session: FragmentType<typeof COMPAT_SESSION_FRAGMENT>;
|
||||
}> = ({ session }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
const data = useFragment(
|
||||
COMPAT_SESSION_FRAGMENT,
|
||||
session,
|
||||
) as CompatSessionType;
|
||||
const endCompatSession = useSetAtom(endCompatSessionFamily(data.id));
|
||||
|
||||
const onSessionEnd = (): void => {
|
||||
@@ -82,45 +95,24 @@ const CompatSession: React.FC<{
|
||||
};
|
||||
|
||||
return (
|
||||
<Block>
|
||||
<Body>
|
||||
Started: <DateTime datetime={data.createdAt} />
|
||||
</Body>
|
||||
{data.finishedAt ? (
|
||||
<div className="text-alert font-semibold">
|
||||
Finished: <DateTime datetime={data.finishedAt} />
|
||||
</div>
|
||||
) : null}
|
||||
<Body>
|
||||
Device ID: <Code>{data.deviceId}</Code>
|
||||
</Body>
|
||||
{data.ssoLogin && <CompatSsoLogin login={data.ssoLogin} />}
|
||||
{data.finishedAt ? null : (
|
||||
<Session
|
||||
id={data.id}
|
||||
name={data.deviceId}
|
||||
createdAt={data.createdAt}
|
||||
finishedAt={data.finishedAt || undefined}
|
||||
clientName={data.ssoLogin.redirectUri}
|
||||
>
|
||||
{!data.finishedAt && (
|
||||
<Button
|
||||
className="mt-2"
|
||||
size="sm"
|
||||
disabled={pending}
|
||||
onClick={onSessionEnd}
|
||||
kind="destructive"
|
||||
size="sm"
|
||||
onClick={onSessionEnd}
|
||||
disabled={pending}
|
||||
>
|
||||
End session
|
||||
</Button>
|
||||
)}
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
const CompatSsoLogin: React.FC<{
|
||||
login: FragmentType<typeof LOGIN_FRAGMENT>;
|
||||
}> = ({ login }) => {
|
||||
const data = useFragment(LOGIN_FRAGMENT, login);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Body>
|
||||
Redirect URI: <Bold>{data.redirectUri}</Bold>
|
||||
</Body>
|
||||
</>
|
||||
</Session>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.
|
||||
|
||||
import { create } from "react-test-renderer";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { FragmentType } from "../gql/fragment-masking";
|
||||
|
||||
import DateTime from "./DateTime";
|
||||
import OAuth2Session, { OAUTH2_SESSION_FRAGMENT } from "./OAuth2Session";
|
||||
|
||||
// Mock out datetime to avoid timezones/date formatting
|
||||
vi.mock("./DateTime", () => {
|
||||
const MockDateTime: typeof DateTime = ({ datetime }) => (
|
||||
<code>{datetime.toString()}</code>
|
||||
);
|
||||
return { default: MockDateTime };
|
||||
});
|
||||
|
||||
describe("<OAuth2Session />", () => {
|
||||
const defaultProps = {
|
||||
session: {
|
||||
id: "session-id",
|
||||
scope:
|
||||
"openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:abcd1234",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
client: {
|
||||
id: "test-id",
|
||||
clientId: "test-client-id",
|
||||
clientName: "Element",
|
||||
clientUri: "https://element.io",
|
||||
},
|
||||
} as FragmentType<typeof OAUTH2_SESSION_FRAGMENT>,
|
||||
};
|
||||
|
||||
const finishedAt = "2023-06-29T03:35:19.451292+00:00";
|
||||
|
||||
it("renders an active session", () => {
|
||||
const component = create(<OAuth2Session {...defaultProps} />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished session", () => {
|
||||
const finishedSession = {
|
||||
...defaultProps.session,
|
||||
finishedAt,
|
||||
};
|
||||
const component = create(<OAuth2Session session={finishedSession} />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -18,14 +18,13 @@ 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 { getDeviceIdFromScope } from "../utils/deviceIdFromScope";
|
||||
|
||||
import Block from "./Block";
|
||||
import DateTime from "./DateTime";
|
||||
import Typography, { Body, Bold, Code } from "./Typography";
|
||||
// import LoadingSpinner from "./LoadingSpinner/LoadingSpinner";
|
||||
import { Session } from "./Session";
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const OAUTH2_SESSION_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment OAuth2Session_session on Oauth2Session {
|
||||
id
|
||||
scope
|
||||
@@ -40,6 +39,19 @@ const FRAGMENT = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
type Oauth2SessionType = {
|
||||
id: string;
|
||||
scope: string;
|
||||
createdAt: string;
|
||||
finishedAt: string | null;
|
||||
client: {
|
||||
id: string;
|
||||
clientId: string;
|
||||
clientName: string;
|
||||
clientUri: string;
|
||||
};
|
||||
};
|
||||
|
||||
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
|
||||
mutation EndOAuth2Session($id: ID!) {
|
||||
endOauth2Session(input: { oauth2SessionId: $id }) {
|
||||
@@ -65,87 +77,48 @@ const endSessionFamily = atomFamily((id: string) => {
|
||||
});
|
||||
|
||||
type Props = {
|
||||
session: FragmentType<typeof FRAGMENT>;
|
||||
};
|
||||
|
||||
const API_SCOPE = "urn:matrix:org.matrix.msc2967.client:api:*";
|
||||
const DEVICE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:";
|
||||
|
||||
const Scope: React.FC<{ scope: string }> = ({ scope }) => {
|
||||
if (scope === "openid") return <>OpenID</>;
|
||||
if (scope === "email") return <>Email</>;
|
||||
if (scope === "profile") return <>Profile</>;
|
||||
if (scope === API_SCOPE) return <>Matrix C-S API </>;
|
||||
|
||||
if (scope.startsWith(DEVICE_PREFIX))
|
||||
return (
|
||||
<>
|
||||
Device <Code>{scope.slice(DEVICE_PREFIX.length)}</Code>
|
||||
</>
|
||||
);
|
||||
|
||||
return <Code>{scope}</Code>;
|
||||
session: FragmentType<typeof OAUTH2_SESSION_FRAGMENT>;
|
||||
};
|
||||
|
||||
const OAuth2Session: React.FC<Props> = ({ session }) => {
|
||||
const [pending, startTransition] = useTransition();
|
||||
const data = useFragment(FRAGMENT, session);
|
||||
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 sessionName = getDeviceIdFromScope(data.scope);
|
||||
|
||||
return (
|
||||
<Block>
|
||||
<Typography variant="body" bold>
|
||||
<Link
|
||||
route={{ type: "client", id: data.client.id }}
|
||||
className="text-links hover:text-links/75"
|
||||
>
|
||||
Client ID: <Code>{data.client.clientId}</Code>
|
||||
</Link>
|
||||
</Typography>
|
||||
{data.client.clientName && (
|
||||
<Body>
|
||||
Client name: <Bold>{data.client.clientName}</Bold>
|
||||
</Body>
|
||||
)}
|
||||
<Typography variant="caption">
|
||||
Started <DateTime datetime={data.createdAt} />
|
||||
</Typography>
|
||||
{data.finishedAt && (
|
||||
<p className="text-alert font-semibold">
|
||||
Finished <DateTime datetime={data.finishedAt} />
|
||||
</p>
|
||||
)}
|
||||
<hr className="my-2 border-t-2 border-grey-300" />
|
||||
<div>
|
||||
<Typography variant="body" bold>
|
||||
Access:
|
||||
</Typography>
|
||||
<ul className="list-disc list-inside">
|
||||
{data.scope.split(" ").map((scope) => (
|
||||
<li key={scope}>
|
||||
<Scope scope={scope} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<Session
|
||||
id={data.id}
|
||||
name={sessionName}
|
||||
createdAt={data.createdAt}
|
||||
finishedAt={data.finishedAt || undefined}
|
||||
clientName={data.client.clientName}
|
||||
>
|
||||
{!data.finishedAt && (
|
||||
<Button
|
||||
kind="destructive"
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
onClick={onSessionEnd}
|
||||
disabled={pending}
|
||||
>
|
||||
{/* @TODO(kerrya) put this back after pending state works properly */}
|
||||
{/* { pending && <LoadingSpinner />} */}
|
||||
End session
|
||||
</Button>
|
||||
)}
|
||||
</Block>
|
||||
</Session>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
.session-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-top: var(--cpd-space-2x);
|
||||
margin-bottom: var(--cpd-space-1x);
|
||||
}
|
||||
|
||||
.session-metadata {
|
||||
color: var(--cpd-color-text-secondary);
|
||||
|
||||
&[data-finished] {
|
||||
color: var(--cpd-color-text-critical-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.session-actions {
|
||||
margin-top: var(--cpd-space-1x);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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.
|
||||
|
||||
import { create } from "react-test-renderer";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import DateTime from "../DateTime";
|
||||
|
||||
import Session from "./Session";
|
||||
|
||||
// Mock out datetime to avoid timezones/date formatting
|
||||
vi.mock("../DateTime", () => {
|
||||
const MockDateTime: typeof DateTime = ({ datetime }) => (
|
||||
<code>{datetime.toString()}</code>
|
||||
);
|
||||
return { default: MockDateTime };
|
||||
});
|
||||
|
||||
describe("<Session />", () => {
|
||||
const defaultProps = {
|
||||
id: "session-id",
|
||||
createdAt: "2023-06-29T03:35:17.451292+00:00",
|
||||
};
|
||||
|
||||
const finishedAt = "2023-06-29T03:35:19.451292+00:00";
|
||||
|
||||
it("renders an active session", () => {
|
||||
const component = create(<Session {...defaultProps} />);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders a finished session", () => {
|
||||
const component = create(
|
||||
<Session {...defaultProps} finishedAt={finishedAt} />,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses session name when truthy", () => {
|
||||
const name = "test session name";
|
||||
const component = create(
|
||||
<Session {...defaultProps} finishedAt={finishedAt} name={name} />,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("uses client name when truthy", () => {
|
||||
const clientName = "Element";
|
||||
const component = create(
|
||||
<Session
|
||||
{...defaultProps}
|
||||
finishedAt={finishedAt}
|
||||
clientName={clientName}
|
||||
/>,
|
||||
);
|
||||
expect(component.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
import { H6, Body } from "@vector-im/compound-web";
|
||||
|
||||
import Block from "../Block";
|
||||
import DateTime from "../DateTime";
|
||||
|
||||
import styles from "./Session.module.css";
|
||||
|
||||
const SessionMetadata: React.FC<React.ComponentProps<typeof Body>> = (
|
||||
props,
|
||||
) => <Body {...props} size="sm" className={styles.sessionMetadata} />;
|
||||
|
||||
export type SessionProps = {
|
||||
id: string;
|
||||
name?: string;
|
||||
createdAt: string;
|
||||
finishedAt?: string;
|
||||
clientName?: string;
|
||||
};
|
||||
const Session: React.FC<React.PropsWithChildren<SessionProps>> = ({
|
||||
id,
|
||||
name,
|
||||
createdAt,
|
||||
finishedAt,
|
||||
clientName,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<Block>
|
||||
<H6 className={styles.sessionName} title={id}>
|
||||
{name || id}
|
||||
</H6>
|
||||
|
||||
<SessionMetadata weight="semibold">
|
||||
Signed in <DateTime datetime={createdAt} />
|
||||
</SessionMetadata>
|
||||
{!!finishedAt && (
|
||||
<SessionMetadata weight="semibold" data-finished={true}>
|
||||
Finished <DateTime datetime={finishedAt} />
|
||||
</SessionMetadata>
|
||||
)}
|
||||
{!!clientName && (
|
||||
<SessionMetadata>
|
||||
Client:{" "}
|
||||
<SessionMetadata weight="semibold" as="span">
|
||||
{clientName}
|
||||
</SessionMetadata>
|
||||
</SessionMetadata>
|
||||
)}
|
||||
{!!children && <div className={styles.sessionActions}>{children}</div>}
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
export default Session;
|
||||
@@ -0,0 +1,123 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<Session /> > renders a finished session 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
session-id
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<code>
|
||||
2023-06-29T03:35:19.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<Session /> > renders an active session 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
session-id
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<Session /> > uses client name when truthy 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
session-id
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<code>
|
||||
2023-06-29T03:35:19.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1g2sj_45 _sessionMetadata_634806"
|
||||
>
|
||||
Client:
|
||||
|
||||
<span
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Element
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<Session /> > uses session name when truthy 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
test session name
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<code>
|
||||
2023-06-29T03:35:19.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
export { default as Session } from "./Session";
|
||||
@@ -0,0 +1,89 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<CompatSession /> > renders a finished session 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
abcd1234
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<code>
|
||||
2023-06-29T03:35:19.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1g2sj_45 _sessionMetadata_634806"
|
||||
>
|
||||
Client:
|
||||
|
||||
<span
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
https://element.io
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<CompatSession /> > renders an active session 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
abcd1234
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1g2sj_45 _sessionMetadata_634806"
|
||||
>
|
||||
Client:
|
||||
|
||||
<span
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
https://element.io
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
className="_sessionActions_634806"
|
||||
>
|
||||
<button
|
||||
className="_button_lls7s_17"
|
||||
data-kind="destructive"
|
||||
data-size="sm"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
End session
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,89 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<OAuth2Session /> > renders a finished session 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
abcd1234
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
data-finished={true}
|
||||
>
|
||||
Finished
|
||||
<code>
|
||||
2023-06-29T03:35:19.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1g2sj_45 _sessionMetadata_634806"
|
||||
>
|
||||
Client:
|
||||
|
||||
<span
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Element
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<OAuth2Session /> > renders an active session 1`] = `
|
||||
<div
|
||||
className="_block_17898c"
|
||||
>
|
||||
<h1
|
||||
className="_font-body-md-semibold_1g2sj_69 _sessionName_634806"
|
||||
title="session-id"
|
||||
>
|
||||
abcd1234
|
||||
</h1>
|
||||
<p
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Signed in
|
||||
<code>
|
||||
2023-06-29T03:35:17.451292+00:00
|
||||
</code>
|
||||
</p>
|
||||
<p
|
||||
className="_font-body-sm-regular_1g2sj_45 _sessionMetadata_634806"
|
||||
>
|
||||
Client:
|
||||
|
||||
<span
|
||||
className="_font-body-sm-semibold_1g2sj_49 _sessionMetadata_634806"
|
||||
>
|
||||
Element
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
className="_sessionActions_634806"
|
||||
>
|
||||
<button
|
||||
className="_button_lls7s_17"
|
||||
data-kind="destructive"
|
||||
data-size="sm"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
End session
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,31 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { getDeviceIdFromScope } from "./deviceIdFromScope";
|
||||
|
||||
describe("getDeviceIdFromScope()", () => {
|
||||
it("returns deviceid when device is part of scope", () => {
|
||||
const scope =
|
||||
"openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:abcd1234";
|
||||
expect(getDeviceIdFromScope(scope)).toEqual("abcd1234");
|
||||
});
|
||||
|
||||
it("returns undefined when device not part of scope", () => {
|
||||
const scope = "openid some:other:scope ";
|
||||
expect(getDeviceIdFromScope(scope)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
const DEVICE_PREFIX = "urn:matrix:org.matrix.msc2967.client:device:";
|
||||
|
||||
/**
|
||||
* Device scopes are suffixed with the deviceId
|
||||
* Isolate the suffix so we can display it
|
||||
* @param scope the full scope of the session
|
||||
* @returns deviceId, or undefined when not a device scope
|
||||
*/
|
||||
export const getDeviceIdFromScope = (scope: string): string | undefined => {
|
||||
const [, deviceId] = scope.split(DEVICE_PREFIX);
|
||||
return deviceId;
|
||||
};
|
||||
Reference in new issue
Block a user