diff --git a/crates/graphql/src/model/mod.rs b/crates/graphql/src/model/mod.rs index f5d018e7..2af32a1f 100644 --- a/crates/graphql/src/model/mod.rs +++ b/crates/graphql/src/model/mod.rs @@ -22,6 +22,7 @@ mod node; mod oauth; mod upstream_oauth; mod users; +mod viewer; pub use self::{ browser_sessions::{Authentication, BrowserSession}, @@ -31,6 +32,7 @@ pub use self::{ oauth::{OAuth2Client, OAuth2Consent, OAuth2Session}, upstream_oauth::{UpstreamOAuth2Link, UpstreamOAuth2Provider}, users::{User, UserEmail}, + viewer::{Anonymous, Viewer, ViewerSession}, }; /// An object with a creation date. diff --git a/crates/graphql/src/model/node.rs b/crates/graphql/src/model/node.rs index 92375b87..d1b8f762 100644 --- a/crates/graphql/src/model/node.rs +++ b/crates/graphql/src/model/node.rs @@ -18,8 +18,8 @@ use thiserror::Error; use ulid::Ulid; use super::{ - Authentication, BrowserSession, CompatSession, CompatSsoLogin, OAuth2Client, OAuth2Session, - UpstreamOAuth2Link, UpstreamOAuth2Provider, User, UserEmail, + Anonymous, Authentication, BrowserSession, CompatSession, CompatSsoLogin, OAuth2Client, + OAuth2Session, UpstreamOAuth2Link, UpstreamOAuth2Provider, User, UserEmail, }; #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -116,6 +116,7 @@ impl NodeType { #[derive(Interface)] #[graphql(field(name = "id", desc = "ID of the object.", type = "ID"))] pub enum Node { + Anonymous(Box), Authentication(Box), BrowserSession(Box), CompatSession(Box), diff --git a/crates/graphql/src/model/viewer/anonymous.rs b/crates/graphql/src/model/viewer/anonymous.rs new file mode 100644 index 00000000..29007aeb --- /dev/null +++ b/crates/graphql/src/model/viewer/anonymous.rs @@ -0,0 +1,26 @@ +// 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. + +use async_graphql::{Object, ID}; + +/// An anonymous viewer +#[derive(Default, Clone, Copy)] +pub struct Anonymous; + +#[Object] +impl Anonymous { + pub async fn id(&self) -> ID { + "anonymous".into() + } +} diff --git a/crates/graphql/src/model/viewer/mod.rs b/crates/graphql/src/model/viewer/mod.rs new file mode 100644 index 00000000..056aa24d --- /dev/null +++ b/crates/graphql/src/model/viewer/mod.rs @@ -0,0 +1,54 @@ +// 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. + +use async_graphql::Union; + +use crate::model::{BrowserSession, User}; + +mod anonymous; +pub use self::anonymous::Anonymous; + +/// Represents the current viewer +#[derive(Union)] +pub enum Viewer { + User(User), + Anonymous(Anonymous), +} + +impl Viewer { + pub fn user(user: mas_data_model::User) -> Self { + Self::User(User(user)) + } + + pub fn anonymous() -> Self { + Self::Anonymous(Anonymous) + } +} + +/// Represents the current viewer's session +#[derive(Union)] +pub enum ViewerSession { + BrowserSession(BrowserSession), + Anonymous(Anonymous), +} + +impl ViewerSession { + pub fn browser_session(session: mas_data_model::BrowserSession) -> Self { + Self::BrowserSession(BrowserSession(session)) + } + + pub fn anonymous() -> Self { + Self::Anonymous(Anonymous) + } +} diff --git a/crates/graphql/src/query.rs b/crates/graphql/src/query/mod.rs similarity index 56% rename from crates/graphql/src/query.rs rename to crates/graphql/src/query/mod.rs index bc3dcf6e..6c379fa0 100644 --- a/crates/graphql/src/query.rs +++ b/crates/graphql/src/query/mod.rs @@ -12,25 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use async_graphql::{ - connection::{query, Connection, Edge, OpaqueCursor}, - Context, Description, Object, ID, -}; -use mas_storage::Pagination; +use async_graphql::{Context, MergedObject, Object, ID}; use crate::{ - model::{ - BrowserSession, Cursor, Node, NodeCursor, NodeType, OAuth2Client, UpstreamOAuth2Link, - UpstreamOAuth2Provider, User, UserEmail, - }, + model::{Anonymous, BrowserSession, Node, NodeType, OAuth2Client, User, UserEmail}, state::ContextExt, }; +mod upstream_oauth; +mod viewer; + +use self::{upstream_oauth::UpstreamOAuthQuery, viewer::ViewerQuery}; + /// The query root of the GraphQL interface. -#[derive(Default, Description)] -pub struct RootQuery { - _private: (), -} +#[derive(Default, MergedObject)] +pub struct RootQuery(BaseQuery, UpstreamOAuthQuery, ViewerQuery); impl RootQuery { #[must_use] @@ -39,9 +35,14 @@ impl RootQuery { } } -#[Object(use_type_description)] -impl RootQuery { +#[derive(Default)] +struct BaseQuery; + +// TODO: move the rest of the queries in separate modules +#[Object] +impl BaseQuery { /// Get the current logged in browser session + #[graphql(deprecation = "Use `viewerSession` instead.")] async fn current_browser_session( &self, ctx: &Context<'_>, @@ -54,6 +55,7 @@ impl RootQuery { } /// Get the current logged in user + #[graphql(deprecation = "Use `viewer` instead.")] async fn current_user(&self, ctx: &Context<'_>) -> Result, async_graphql::Error> { let requester = ctx.requester(); Ok(requester.user().cloned().map(User::from)) @@ -141,99 +143,13 @@ impl RootQuery { Ok(user_email.map(UserEmail)) } - /// Fetch an upstream OAuth 2.0 link by its ID. - async fn upstream_oauth2_link( - &self, - ctx: &Context<'_>, - id: ID, - ) -> Result, async_graphql::Error> { - let state = ctx.state(); - let id = NodeType::UpstreamOAuth2Link.extract_ulid(&id)?; - let requester = ctx.requester(); - - let Some(current_user) = requester.user() else { return Ok(None) }; - let mut repo = state.repository().await?; - - let link = repo.upstream_oauth_link().lookup(id).await?; - - // Ensure that the link belongs to the current user - let link = link.filter(|link| link.user_id == Some(current_user.id)); - - Ok(link.map(UpstreamOAuth2Link::new)) - } - - /// Fetch an upstream OAuth 2.0 provider by its ID. - async fn upstream_oauth2_provider( - &self, - ctx: &Context<'_>, - id: ID, - ) -> Result, async_graphql::Error> { - let state = ctx.state(); - let id = NodeType::UpstreamOAuth2Provider.extract_ulid(&id)?; - - let mut repo = state.repository().await?; - let provider = repo.upstream_oauth_provider().lookup(id).await?; - repo.cancel().await?; - - Ok(provider.map(UpstreamOAuth2Provider::new)) - } - - /// Get a list of upstream OAuth 2.0 providers. - async fn upstream_oauth2_providers( - &self, - ctx: &Context<'_>, - - #[graphql(desc = "Returns the elements in the list that come after the cursor.")] - after: Option, - #[graphql(desc = "Returns the elements in the list that come before the cursor.")] - before: Option, - #[graphql(desc = "Returns the first *n* elements from the list.")] first: Option, - #[graphql(desc = "Returns the last *n* elements from the list.")] last: Option, - ) -> Result, async_graphql::Error> { - let state = ctx.state(); - let mut repo = state.repository().await?; - - query( - after, - before, - first, - last, - |after, before, first, last| async move { - let after_id = after - .map(|x: OpaqueCursor| { - x.extract_for_type(NodeType::UpstreamOAuth2Provider) - }) - .transpose()?; - let before_id = before - .map(|x: OpaqueCursor| { - x.extract_for_type(NodeType::UpstreamOAuth2Provider) - }) - .transpose()?; - let pagination = Pagination::try_new(before_id, after_id, first, last)?; - - let page = repo - .upstream_oauth_provider() - .list_paginated(pagination) - .await?; - - repo.cancel().await?; - - let mut connection = Connection::new(page.has_previous_page, page.has_next_page); - connection.edges.extend(page.edges.into_iter().map(|p| { - Edge::new( - OpaqueCursor(NodeCursor(NodeType::UpstreamOAuth2Provider, p.id)), - UpstreamOAuth2Provider::new(p), - ) - })); - - Ok::<_, async_graphql::Error>(connection) - }, - ) - .await - } - /// Fetches an object given its ID. async fn node(&self, ctx: &Context<'_>, id: ID) -> Result, async_graphql::Error> { + // Special case for the anonymous user + if id.as_str() == "anonymous" { + return Ok(Some(Node::Anonymous(Box::new(Anonymous)))); + } + let (node_type, _id) = NodeType::from_id(&id)?; let ret = match node_type { @@ -243,12 +159,12 @@ impl RootQuery { | NodeType::CompatSsoLogin | NodeType::OAuth2Session => None, - NodeType::UpstreamOAuth2Provider => self + NodeType::UpstreamOAuth2Provider => UpstreamOAuthQuery .upstream_oauth2_provider(ctx, id) .await? .map(|c| Node::UpstreamOAuth2Provider(Box::new(c))), - NodeType::UpstreamOAuth2Link => self + NodeType::UpstreamOAuth2Link => UpstreamOAuthQuery .upstream_oauth2_link(ctx, id) .await? .map(|c| Node::UpstreamOAuth2Link(Box::new(c))), diff --git a/crates/graphql/src/query/upstream_oauth.rs b/crates/graphql/src/query/upstream_oauth.rs new file mode 100644 index 00000000..f845ecb1 --- /dev/null +++ b/crates/graphql/src/query/upstream_oauth.rs @@ -0,0 +1,121 @@ +// 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. + +use async_graphql::{ + connection::{query, Connection, Edge, OpaqueCursor}, + Context, Object, ID, +}; +use mas_storage::Pagination; + +use crate::{ + model::{Cursor, NodeCursor, NodeType, UpstreamOAuth2Link, UpstreamOAuth2Provider}, + state::ContextExt, +}; + +#[derive(Default)] +pub struct UpstreamOAuthQuery; + +#[Object] +impl UpstreamOAuthQuery { + /// Fetch an upstream OAuth 2.0 link by its ID. + pub async fn upstream_oauth2_link( + &self, + ctx: &Context<'_>, + id: ID, + ) -> Result, async_graphql::Error> { + let state = ctx.state(); + let id = NodeType::UpstreamOAuth2Link.extract_ulid(&id)?; + let requester = ctx.requester(); + + let Some(current_user) = requester.user() else { return Ok(None) }; + let mut repo = state.repository().await?; + + let link = repo.upstream_oauth_link().lookup(id).await?; + + // Ensure that the link belongs to the current user + let link = link.filter(|link| link.user_id == Some(current_user.id)); + + Ok(link.map(UpstreamOAuth2Link::new)) + } + + /// Fetch an upstream OAuth 2.0 provider by its ID. + pub async fn upstream_oauth2_provider( + &self, + ctx: &Context<'_>, + id: ID, + ) -> Result, async_graphql::Error> { + let state = ctx.state(); + let id = NodeType::UpstreamOAuth2Provider.extract_ulid(&id)?; + + let mut repo = state.repository().await?; + let provider = repo.upstream_oauth_provider().lookup(id).await?; + repo.cancel().await?; + + Ok(provider.map(UpstreamOAuth2Provider::new)) + } + + /// Get a list of upstream OAuth 2.0 providers. + async fn upstream_oauth2_providers( + &self, + ctx: &Context<'_>, + + #[graphql(desc = "Returns the elements in the list that come after the cursor.")] + after: Option, + #[graphql(desc = "Returns the elements in the list that come before the cursor.")] + before: Option, + #[graphql(desc = "Returns the first *n* elements from the list.")] first: Option, + #[graphql(desc = "Returns the last *n* elements from the list.")] last: Option, + ) -> Result, async_graphql::Error> { + let state = ctx.state(); + let mut repo = state.repository().await?; + + query( + after, + before, + first, + last, + |after, before, first, last| async move { + let after_id = after + .map(|x: OpaqueCursor| { + x.extract_for_type(NodeType::UpstreamOAuth2Provider) + }) + .transpose()?; + let before_id = before + .map(|x: OpaqueCursor| { + x.extract_for_type(NodeType::UpstreamOAuth2Provider) + }) + .transpose()?; + let pagination = Pagination::try_new(before_id, after_id, first, last)?; + + let page = repo + .upstream_oauth_provider() + .list_paginated(pagination) + .await?; + + repo.cancel().await?; + + let mut connection = Connection::new(page.has_previous_page, page.has_next_page); + connection.edges.extend(page.edges.into_iter().map(|p| { + Edge::new( + OpaqueCursor(NodeCursor(NodeType::UpstreamOAuth2Provider, p.id)), + UpstreamOAuth2Provider::new(p), + ) + })); + + Ok::<_, async_graphql::Error>(connection) + }, + ) + .await + } +} diff --git a/crates/graphql/src/query/viewer.rs b/crates/graphql/src/query/viewer.rs new file mode 100644 index 00000000..9a6cd24f --- /dev/null +++ b/crates/graphql/src/query/viewer.rs @@ -0,0 +1,47 @@ +// 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. + +use async_graphql::{Context, Object}; + +use crate::{ + model::{Viewer, ViewerSession}, + state::ContextExt, + Requester, +}; + +#[derive(Default)] +pub struct ViewerQuery; + +#[Object] +impl ViewerQuery { + /// Get the viewer + async fn viewer(&self, ctx: &Context<'_>) -> Viewer { + let requester = ctx.requester(); + + match requester { + Requester::BrowserSession(session) => Viewer::user(session.user.clone()), + Requester::Anonymous => Viewer::anonymous(), + } + } + + /// Get the viewer's session + async fn viewer_session(&self, ctx: &Context<'_>) -> ViewerSession { + let requester = ctx.requester(); + + match requester { + Requester::BrowserSession(session) => ViewerSession::browser_session(session.clone()), + Requester::Anonymous => ViewerSession::anonymous(), + } + } +} diff --git a/frontend/schema.graphql b/frontend/schema.graphql index fcdf7fe1..2fb47373 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -12,6 +12,10 @@ input AddEmailInput { userId: ID! } +type Anonymous implements Node { + id: ID! +} + """ An authentication records when a user enter their credential in a browser session. @@ -331,10 +335,11 @@ type RootQuery { Get the current logged in browser session """ currentBrowserSession: BrowserSession + @deprecated(reason: "Use `viewerSession` instead.") """ Get the current logged in user """ - currentUser: User + currentUser: User @deprecated(reason: "Use `viewer` instead.") """ Fetch an OAuth 2.0 client by its ID. """ @@ -352,6 +357,10 @@ type RootQuery { """ userEmail(id: ID!): UserEmail """ + Fetches an object given its ID. + """ + node(id: ID!): Node + """ Fetch an upstream OAuth 2.0 link by its ID. """ upstreamOauth2Link(id: ID!): UpstreamOAuth2Link @@ -369,9 +378,13 @@ type RootQuery { last: Int ): UpstreamOAuth2ProviderConnection! """ - Fetches an object given its ID. + Get the viewer """ - node(id: ID!): Node + viewer: Viewer! + """ + Get the viewer's session + """ + viewerSession: ViewerSession! } """ @@ -622,6 +635,16 @@ input VerifyEmailInput { code: String! } +""" +Represents the current viewer +""" +union Viewer = User | Anonymous + +""" +Represents the current viewer's session +""" +union ViewerSession = BrowserSession | Anonymous + schema { query: RootQuery mutation: RootMutations diff --git a/frontend/src/gql/gql.ts b/frontend/src/gql/gql.ts index 1e40efa4..62d41626 100644 --- a/frontend/src/gql/gql.ts +++ b/frontend/src/gql/gql.ts @@ -20,7 +20,7 @@ const documents = { "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n": types.OAuth2Session_SessionFragmentDoc, "\n fragment OAuth2SessionList_user on User {\n oauth2Sessions(first: $count, after: $cursor) {\n edges {\n cursor\n node {\n id\n ...OAuth2Session_session\n }\n }\n }\n }\n": types.OAuth2SessionList_UserFragmentDoc, "\n query BrowserSessionQuery($id: ID!) {\n browserSession(id: $id) {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n user {\n id\n username\n }\n }\n }\n": types.BrowserSessionQueryDocument, - "\n query HomeQuery($count: Int!, $cursor: String) {\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n": types.HomeQueryDocument, + "\n query HomeQuery($count: Int!, $cursor: String) {\n # eslint-disable-next-line @graphql-eslint/no-deprecated\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n": types.HomeQueryDocument, "\n query OAuth2ClientQuery($id: ID!) {\n oauth2Client(id: $id) {\n id\n clientId\n clientName\n clientUri\n tosUri\n policyUri\n redirectUris\n }\n }\n": types.OAuth2ClientQueryDocument, }; @@ -69,7 +69,7 @@ export function graphql(source: "\n query BrowserSessionQuery($id: ID!) {\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 HomeQuery($count: Int!, $cursor: String) {\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n"): (typeof documents)["\n query HomeQuery($count: Int!, $cursor: String) {\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n"]; +export function graphql(source: "\n query HomeQuery($count: Int!, $cursor: String) {\n # eslint-disable-next-line @graphql-eslint/no-deprecated\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\n }\n }\n }\n"): (typeof documents)["\n query HomeQuery($count: Int!, $cursor: String) {\n # eslint-disable-next-line @graphql-eslint/no-deprecated\n currentBrowserSession {\n id\n user {\n id\n username\n\n ...CompatSsoLoginList_user\n ...BrowserSessionList_user\n ...OAuth2SessionList_user\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 a07c0a6a..8bcdbeba 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -30,6 +30,11 @@ export type AddEmailInput = { userId: Scalars['ID']; }; +export type Anonymous = Node & { + __typename?: 'Anonymous'; + id: Scalars['ID']; +}; + /** * An authentication records when a user enter their credential in a browser * session. @@ -249,9 +254,15 @@ export type RootQuery = { __typename?: 'RootQuery'; /** Fetch a browser session by its ID. */ browserSession?: Maybe; - /** Get the current logged in browser session */ + /** + * Get the current logged in browser session + * @deprecated Use `viewerSession` instead. + */ currentBrowserSession?: Maybe; - /** Get the current logged in user */ + /** + * Get the current logged in user + * @deprecated Use `viewer` instead. + */ currentUser?: Maybe; /** Fetches an object given its ID. */ node?: Maybe; @@ -267,6 +278,10 @@ export type RootQuery = { user?: Maybe; /** Fetch a user email by its ID. */ userEmail?: Maybe; + /** Get the viewer */ + viewer: Viewer; + /** Get the viewer's session */ + viewerSession: ViewerSession; }; @@ -501,6 +516,12 @@ export type VerifyEmailInput = { userEmailId: Scalars['ID']; }; +/** Represents the current viewer */ +export type Viewer = Anonymous | User; + +/** Represents the current viewer's session */ +export type ViewerSession = Anonymous | BrowserSession; + export type BrowserSession_SessionFragment = { __typename?: 'BrowserSession', id: string, createdAt: any, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: any } | null } & { ' $fragmentName'?: 'BrowserSession_SessionFragment' }; export type BrowserSessionList_UserFragment = { __typename?: 'User', browserSessions: { __typename?: 'BrowserSessionConnection', edges: Array<{ __typename?: 'BrowserSessionEdge', cursor: string, node: ( diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index f59b9571..6948f7cc 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -9,6 +9,29 @@ export default { }, subscriptionType: null, types: [ + { + kind: "OBJECT", + name: "Anonymous", + fields: [ + { + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + ], + interfaces: [ + { + kind: "INTERFACE", + name: "Node", + }, + ], + }, { kind: "OBJECT", name: "Authentication", @@ -477,6 +500,10 @@ export default { ], interfaces: [], possibleTypes: [ + { + kind: "OBJECT", + name: "Anonymous", + }, { kind: "OBJECT", name: "Authentication", @@ -1081,6 +1108,30 @@ export default { }, ], }, + { + name: "viewer", + type: { + kind: "NON_NULL", + ofType: { + kind: "UNION", + name: "Viewer", + ofType: null, + }, + }, + args: [], + }, + { + name: "viewerSession", + type: { + kind: "NON_NULL", + ofType: { + kind: "UNION", + name: "ViewerSession", + ofType: null, + }, + }, + args: [], + }, ], interfaces: [], }, @@ -1785,6 +1836,34 @@ export default { ], interfaces: [], }, + { + kind: "UNION", + name: "Viewer", + possibleTypes: [ + { + kind: "OBJECT", + name: "Anonymous", + }, + { + kind: "OBJECT", + name: "User", + }, + ], + }, + { + kind: "UNION", + name: "ViewerSession", + possibleTypes: [ + { + kind: "OBJECT", + name: "Anonymous", + }, + { + kind: "OBJECT", + name: "BrowserSession", + }, + ], + }, { kind: "SCALAR", name: "Any", diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 51d349db..0b9ed602 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -23,6 +23,7 @@ import { graphql } from "../gql"; const QUERY = graphql(/* GraphQL */ ` query HomeQuery($count: Int!, $cursor: String) { + # eslint-disable-next-line @graphql-eslint/no-deprecated currentBrowserSession { id user {