diff --git a/frontend/src/components/SessionDetail/SessionDetail.tsx b/frontend/src/components/SessionDetail/SessionDetail.tsx
index cfb2bd55..a268023c 100644
--- a/frontend/src/components/SessionDetail/SessionDetail.tsx
+++ b/frontend/src/components/SessionDetail/SessionDetail.tsx
@@ -12,15 +12,71 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import { Alert, Body } from "@vector-im/compound-web";
+import { useAtomValue } from "jotai";
+import { atomFamily } from "jotai/utils";
+import { atomWithQuery } from "jotai-urql";
+import { useRef } from "react";
+
+import { Link } from "../../Router";
+import { graphql } from "../../gql/gql";
+import CompatSession from "../CompatSession";
+import OAuth2Session from "../OAuth2Session";
+
+const QUERY = graphql(/* GraphQL */ `
+ query SessionQuery($userId: ID!, $deviceId: String!) {
+ session(userId: $userId, deviceId: $deviceId) {
+ __typename
+ ...CompatSession_session
+ ...OAuth2Session_session
+ }
+ }
+`);
+
+const sessionFamily = atomFamily(
+ ({ userId, deviceId }: { userId: string; deviceId: string }) => {
+ const sessionQueryAtom = atomWithQuery({
+ query: QUERY,
+ getVariables: () => ({ userId, deviceId }),
+ });
+
+ return sessionQueryAtom;
+ },
+);
+
const SessionDetail: React.FC<{
deviceId: string;
userId: string;
}> = ({ deviceId, userId }) => {
+ const props = useRef({ userId, deviceId });
+ const result = useAtomValue(sessionFamily(props.current));
+
+ const session = result.data?.session;
+
+ if (!session) {
+ // TODO put a back button here
+ return (
+
+ Cannot find session: {deviceId}
+
+ Go back
+
+
+ );
+ }
+
+ const sessionType = session.__typename;
+
+ if (sessionType === "Oauth2Session") {
+ return ;
+ } else if (sessionType === "CompatSession") {
+ return ;
+ }
+
return (
- <>
- deviceId: {deviceId}
- userId: {userId}
- >
+
+ Unexpected session type
+
);
};
diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts
index 05745851..fb9b8e50 100644
--- a/frontend/src/gql/gql.ts
+++ b/frontend/src/gql/gql.ts
@@ -37,6 +37,8 @@ const documents = {
types.EndOAuth2SessionDocument,
"\n query OAuth2SessionListQuery(\n $userId: ID!\n $state: Oauth2SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n state: $state\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
types.OAuth2SessionListQueryDocument,
+ "\n query SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n":
+ types.SessionQueryDocument,
"\n fragment UnverifiedEmailAlert on User {\n id\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n }\n":
types.UnverifiedEmailAlertFragmentDoc,
"\n fragment UserEmail_email on UserEmail {\n id\n email\n confirmedAt\n }\n":
@@ -159,6 +161,12 @@ export function graphql(
export function graphql(
source: "\n query OAuth2SessionListQuery(\n $userId: ID!\n $state: Oauth2SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n state: $state\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n",
): (typeof documents)["\n query OAuth2SessionListQuery(\n $userId: ID!\n $state: Oauth2SessionState\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\n state: $state\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\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 SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\n }\n }\n",
+): (typeof documents)["\n query SessionQuery($userId: ID!, $deviceId: String!) {\n session(userId: $userId, deviceId: $deviceId) {\n __typename\n ...CompatSession_session\n ...OAuth2Session_session\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 b3142785..807e27f9 100644
--- a/frontend/src/gql/graphql.ts
+++ b/frontend/src/gql/graphql.ts
@@ -1146,6 +1146,27 @@ export type OAuth2SessionListQueryQuery = {
} | null;
};
+export type SessionQueryQueryVariables = Exact<{
+ userId: Scalars["ID"]["input"];
+ deviceId: Scalars["String"]["input"];
+}>;
+
+export type SessionQueryQuery = {
+ __typename?: "Query";
+ session?:
+ | ({ __typename: "CompatSession" } & {
+ " $fragmentRefs"?: {
+ CompatSession_SessionFragment: CompatSession_SessionFragment;
+ };
+ })
+ | ({ __typename: "Oauth2Session" } & {
+ " $fragmentRefs"?: {
+ OAuth2Session_SessionFragment: OAuth2Session_SessionFragment;
+ };
+ })
+ | null;
+};
+
export type UnverifiedEmailAlertFragment = {
__typename?: "User";
id: string;
@@ -2891,6 +2912,160 @@ export const OAuth2SessionListQueryDocument = {
OAuth2SessionListQueryQuery,
OAuth2SessionListQueryQueryVariables
>;
+export const SessionQueryDocument = {
+ kind: "Document",
+ definitions: [
+ {
+ kind: "OperationDefinition",
+ operation: "query",
+ name: { kind: "Name", value: "SessionQuery" },
+ variableDefinitions: [
+ {
+ kind: "VariableDefinition",
+ variable: {
+ kind: "Variable",
+ name: { kind: "Name", value: "userId" },
+ },
+ type: {
+ kind: "NonNullType",
+ type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
+ },
+ },
+ {
+ kind: "VariableDefinition",
+ variable: {
+ kind: "Variable",
+ name: { kind: "Name", value: "deviceId" },
+ },
+ type: {
+ kind: "NonNullType",
+ type: {
+ kind: "NamedType",
+ name: { kind: "Name", value: "String" },
+ },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: "SelectionSet",
+ selections: [
+ {
+ kind: "Field",
+ name: { kind: "Name", value: "session" },
+ arguments: [
+ {
+ kind: "Argument",
+ name: { kind: "Name", value: "userId" },
+ value: {
+ kind: "Variable",
+ name: { kind: "Name", value: "userId" },
+ },
+ },
+ {
+ kind: "Argument",
+ name: { kind: "Name", value: "deviceId" },
+ value: {
+ kind: "Variable",
+ name: { kind: "Name", value: "deviceId" },
+ },
+ },
+ ],
+ selectionSet: {
+ kind: "SelectionSet",
+ selections: [
+ { kind: "Field", name: { kind: "Name", value: "__typename" } },
+ {
+ kind: "FragmentSpread",
+ name: { kind: "Name", value: "CompatSession_session" },
+ },
+ {
+ kind: "FragmentSpread",
+ name: { kind: "Name", value: "OAuth2Session_session" },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ kind: "FragmentDefinition",
+ name: { kind: "Name", value: "CompatSession_sso_login" },
+ typeCondition: {
+ kind: "NamedType",
+ name: { kind: "Name", value: "CompatSsoLogin" },
+ },
+ selectionSet: {
+ kind: "SelectionSet",
+ selections: [
+ { kind: "Field", name: { kind: "Name", value: "id" } },
+ { kind: "Field", name: { kind: "Name", value: "redirectUri" } },
+ ],
+ },
+ },
+ {
+ kind: "FragmentDefinition",
+ name: { kind: "Name", value: "CompatSession_session" },
+ typeCondition: {
+ kind: "NamedType",
+ name: { kind: "Name", value: "CompatSession" },
+ },
+ selectionSet: {
+ kind: "SelectionSet",
+ selections: [
+ { kind: "Field", name: { kind: "Name", value: "id" } },
+ { kind: "Field", name: { kind: "Name", value: "createdAt" } },
+ { kind: "Field", name: { kind: "Name", value: "deviceId" } },
+ { kind: "Field", name: { kind: "Name", value: "finishedAt" } },
+ {
+ kind: "Field",
+ name: { kind: "Name", value: "ssoLogin" },
+ selectionSet: {
+ kind: "SelectionSet",
+ selections: [
+ { kind: "Field", name: { kind: "Name", value: "id" } },
+ {
+ kind: "FragmentSpread",
+ name: { kind: "Name", value: "CompatSession_sso_login" },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ {
+ kind: "FragmentDefinition",
+ name: { kind: "Name", value: "OAuth2Session_session" },
+ typeCondition: {
+ kind: "NamedType",
+ name: { kind: "Name", value: "Oauth2Session" },
+ },
+ selectionSet: {
+ kind: "SelectionSet",
+ selections: [
+ { kind: "Field", name: { kind: "Name", value: "id" } },
+ { kind: "Field", name: { kind: "Name", value: "scope" } },
+ { kind: "Field", name: { kind: "Name", value: "createdAt" } },
+ { kind: "Field", name: { kind: "Name", value: "finishedAt" } },
+ {
+ kind: "Field",
+ name: { kind: "Name", value: "client" },
+ selectionSet: {
+ kind: "SelectionSet",
+ selections: [
+ { kind: "Field", name: { kind: "Name", value: "id" } },
+ { kind: "Field", name: { kind: "Name", value: "clientId" } },
+ { kind: "Field", name: { kind: "Name", value: "clientName" } },
+ { kind: "Field", name: { kind: "Name", value: "clientUri" } },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+} as unknown as DocumentNode;
export const RemoveEmailDocument = {
kind: "Document",
definitions: [