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:
Kerry authored and GitHub committed 2023-08-22 15:37:28 +12:00
1 parent ff1f33d94a
commit a068e496f0
16 files changed
+742 -106

No files matched your search

+3
View File
@@ -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();
});
});
+31 -39
View File
@@ -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();
});
});
+36 -63
View File
@@ -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>
`;
+15
View File
@@ -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();
});
});
+27
View File
@@ -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;
};