1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Frontend cleanups

Mainly:

 - better handling of GraphQL errors
 - better logout state
 - dependencies update
 - a way to end browser sessions in the GraphQL API
This commit is contained in:
Quentin Gliech
2023-06-20 15:05:48 +02:00
parent ebb87f0a5e
commit f67cc0d6d0
24 changed files with 1072 additions and 230 deletions

View File

@ -0,0 +1,103 @@
// 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 anyhow::Context as _;
use async_graphql::{Context, Enum, InputObject, Object, ID};
use mas_storage::RepositoryAccess;
use crate::{
model::{BrowserSession, NodeType},
state::ContextExt,
};
#[derive(Default)]
pub struct BrowserSessionMutations {
_private: (),
}
/// The input of the `endBrowserSession` mutation.
#[derive(InputObject)]
pub struct EndBrowserSessionInput {
/// The ID of the session to end.
browser_session_id: ID,
}
/// The payload of the `endBrowserSession` mutation.
pub enum EndBrowserSessionPayload {
NotFound,
Ended(mas_data_model::BrowserSession),
}
/// The status of the `endBrowserSession` mutation.
#[derive(Enum, Copy, Clone, PartialEq, Eq, Debug)]
enum EndBrowserSessionStatus {
/// The session was ended.
Ended,
/// The session was not found.
NotFound,
}
#[Object]
impl EndBrowserSessionPayload {
/// The status of the mutation.
async fn status(&self) -> EndBrowserSessionStatus {
match self {
Self::Ended(_) => EndBrowserSessionStatus::Ended,
Self::NotFound => EndBrowserSessionStatus::NotFound,
}
}
/// Returns the ended session.
async fn browser_session(&self) -> Option<BrowserSession> {
match self {
Self::Ended(session) => Some(BrowserSession(session.clone())),
Self::NotFound => None,
}
}
}
#[Object]
impl BrowserSessionMutations {
async fn end_browser_session(
&self,
ctx: &Context<'_>,
input: EndBrowserSessionInput,
) -> Result<EndBrowserSessionPayload, async_graphql::Error> {
let state = ctx.state();
let browser_session_id =
NodeType::BrowserSession.extract_ulid(&input.browser_session_id)?;
let requester = ctx.requester();
let user = requester.user().context("Unauthorized")?;
let mut repo = state.repository().await?;
let clock = state.clock();
let session = repo.browser_session().lookup(browser_session_id).await?;
let Some(session) = session else {
return Ok(EndBrowserSessionPayload::NotFound);
};
if session.user.id != user.id {
return Err(async_graphql::Error::new("Unauthorized"));
}
let session = repo.browser_session().finish(&clock, session).await?;
repo.save().await?;
Ok(EndBrowserSessionPayload::Ended(session))
}
}

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
mod browser_session;
mod compat_session; mod compat_session;
mod oauth2_session; mod oauth2_session;
mod user_email; mod user_email;
@ -24,6 +25,7 @@ pub struct Mutation(
user_email::UserEmailMutations, user_email::UserEmailMutations,
oauth2_session::OAuth2SessionMutations, oauth2_session::OAuth2SessionMutations,
compat_session::CompatSessionMutations, compat_session::CompatSessionMutations,
browser_session::BrowserSessionMutations,
); );
impl Mutation { impl Mutation {

View File

@ -22,7 +22,8 @@
"jotai-location": "^0.5.1", "jotai-location": "^0.5.1",
"jotai-urql": "^0.7.1", "jotai-urql": "^0.7.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"vite-plugin-svgr": "^3.2.0"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^4.0.1", "@graphql-codegen/cli": "^4.0.1",
@ -95,7 +96,6 @@
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
"integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9" "@jridgewell/trace-mapping": "^0.3.9"
@ -344,7 +344,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz",
"integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -353,7 +352,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz",
"integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==",
"dev": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.5", "@babel/code-frame": "^7.22.5",
@ -418,7 +416,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz",
"integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5", "@babel/types": "^7.22.5",
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
@ -457,7 +454,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz",
"integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.22.5", "@babel/compat-data": "^7.22.5",
"@babel/helper-validator-option": "^7.22.5", "@babel/helper-validator-option": "^7.22.5",
@ -533,7 +529,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -542,7 +537,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/template": "^7.22.5", "@babel/template": "^7.22.5",
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
@ -555,7 +549,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
"integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
}, },
@ -590,7 +583,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz",
"integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-module-imports": "^7.22.5", "@babel/helper-module-imports": "^7.22.5",
@ -665,7 +657,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
}, },
@ -689,7 +680,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz",
"integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.22.5" "@babel/types": "^7.22.5"
}, },
@ -717,7 +707,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
"integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -741,7 +730,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz",
"integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/template": "^7.22.5", "@babel/template": "^7.22.5",
"@babel/traverse": "^7.22.5", "@babel/traverse": "^7.22.5",
@ -768,7 +756,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz",
"integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==",
"dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@ -2610,7 +2597,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.5", "@babel/code-frame": "^7.22.5",
"@babel/parser": "^7.22.5", "@babel/parser": "^7.22.5",
@ -2624,7 +2610,6 @@
"version": "7.22.5", "version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz",
"integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.5", "@babel/code-frame": "^7.22.5",
"@babel/generator": "^7.22.5", "@babel/generator": "^7.22.5",
@ -2795,7 +2780,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -2811,7 +2795,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -2827,7 +2810,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -2843,7 +2825,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -2859,7 +2840,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -2875,7 +2855,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -2891,7 +2870,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -2907,7 +2885,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -2923,7 +2900,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -2939,7 +2915,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -2955,7 +2930,6 @@
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -2971,7 +2945,6 @@
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -2987,7 +2960,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -3003,7 +2975,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -3019,7 +2990,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -3035,7 +3005,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -3051,7 +3020,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"netbsd" "netbsd"
@ -3067,7 +3035,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"openbsd" "openbsd"
@ -3083,7 +3050,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"sunos" "sunos"
@ -3099,7 +3065,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -3115,7 +3080,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -3131,7 +3095,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -5170,7 +5133,6 @@
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/set-array": "^1.0.1", "@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/sourcemap-codec": "^1.4.10",
@ -5184,7 +5146,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -5193,7 +5154,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@ -5201,14 +5161,12 @@
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
"dev": true
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.18", "version": "0.3.18",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "3.1.0", "@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14" "@jridgewell/sourcemap-codec": "1.4.14"
@ -5217,8 +5175,7 @@
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14", "version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
"dev": true
}, },
"node_modules/@juggle/resize-observer": { "node_modules/@juggle/resize-observer": {
"version": "3.4.0", "version": "3.4.0",
@ -7572,6 +7529,225 @@
"url": "https://opencollective.com/storybook" "url": "https://opencollective.com/storybook"
} }
}, },
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-7.0.0.tgz",
"integrity": "sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==",
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-remove-jsx-attribute": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-7.0.0.tgz",
"integrity": "sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==",
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-7.0.0.tgz",
"integrity": "sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==",
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-7.0.0.tgz",
"integrity": "sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==",
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-svg-dynamic-title": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-7.0.0.tgz",
"integrity": "sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==",
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-svg-em-dimensions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-7.0.0.tgz",
"integrity": "sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==",
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-transform-react-native-svg": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-7.0.0.tgz",
"integrity": "sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==",
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-plugin-transform-svg-component": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-7.0.0.tgz",
"integrity": "sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/babel-preset": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-7.0.0.tgz",
"integrity": "sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==",
"dependencies": {
"@svgr/babel-plugin-add-jsx-attribute": "^7.0.0",
"@svgr/babel-plugin-remove-jsx-attribute": "^7.0.0",
"@svgr/babel-plugin-remove-jsx-empty-expression": "^7.0.0",
"@svgr/babel-plugin-replace-jsx-attribute-value": "^7.0.0",
"@svgr/babel-plugin-svg-dynamic-title": "^7.0.0",
"@svgr/babel-plugin-svg-em-dimensions": "^7.0.0",
"@svgr/babel-plugin-transform-react-native-svg": "^7.0.0",
"@svgr/babel-plugin-transform-svg-component": "^7.0.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@svgr/core": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/core/-/core-7.0.0.tgz",
"integrity": "sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==",
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "^7.0.0",
"camelcase": "^6.2.0",
"cosmiconfig": "^8.1.3"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@svgr/core/node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@svgr/hast-util-to-babel-ast": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-7.0.0.tgz",
"integrity": "sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==",
"dependencies": {
"@babel/types": "^7.21.3",
"entities": "^4.4.0"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/@svgr/plugin-jsx": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-7.0.0.tgz",
"integrity": "sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==",
"dependencies": {
"@babel/core": "^7.21.3",
"@svgr/babel-preset": "^7.0.0",
"@svgr/hast-util-to-babel-ast": "^7.0.0",
"svg-parser": "^2.0.4"
},
"engines": {
"node": ">=14"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@tabler/icons": { "node_modules/@tabler/icons": {
"version": "2.22.0", "version": "2.22.0",
"resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.22.0.tgz", "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.22.0.tgz",
@ -7845,7 +8021,7 @@
"version": "20.3.1", "version": "20.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz",
"integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==", "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==",
"dev": true "devOptional": true
}, },
"node_modules/@types/node-fetch": { "node_modules/@types/node-fetch": {
"version": "2.6.4", "version": "2.6.4",
@ -8804,8 +8980,7 @@
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
"dev": true
}, },
"node_modules/aria-hidden": { "node_modules/aria-hidden": {
"version": "1.2.3", "version": "1.2.3",
@ -9474,7 +9649,6 @@
"version": "4.21.9", "version": "4.21.9",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
"integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -9716,7 +9890,6 @@
"version": "1.0.30001504", "version": "1.0.30001504",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz",
"integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==", "integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -10415,7 +10588,6 @@
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz",
"integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==",
"dev": true,
"dependencies": { "dependencies": {
"import-fresh": "^3.2.1", "import-fresh": "^3.2.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
@ -10589,7 +10761,6 @@
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
}, },
@ -11132,8 +11303,7 @@
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.433", "version": "1.4.433",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz",
"integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==", "integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ=="
"dev": true
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
@ -11304,7 +11474,6 @@
"version": "0.17.19", "version": "0.17.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
"integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
"esbuild": "bin/esbuild" "esbuild": "bin/esbuild"
@ -11359,7 +11528,6 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -12267,8 +12435,7 @@
"node_modules/estree-walker": { "node_modules/estree-walker": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
"dev": true
}, },
"node_modules/esutils": { "node_modules/esutils": {
"version": "2.0.3", "version": "2.0.3",
@ -12953,7 +13120,6 @@
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@ -13019,7 +13185,6 @@
"version": "1.0.0-beta.2", "version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -13262,7 +13427,6 @@
"version": "11.12.0", "version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@ -14858,7 +15022,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
}, },
@ -15002,7 +15165,6 @@
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true,
"bin": { "bin": {
"jsesc": "bin/jsesc" "jsesc": "bin/jsesc"
}, },
@ -15056,7 +15218,6 @@
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": { "bin": {
"json5": "lib/cli.js" "json5": "lib/cli.js"
}, },
@ -15449,7 +15610,6 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"dependencies": { "dependencies": {
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
@ -15801,8 +15961,7 @@
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"dev": true
}, },
"node_modules/mute-stream": { "node_modules/mute-stream": {
"version": "0.0.8", "version": "0.0.8",
@ -15825,7 +15984,6 @@
"version": "3.3.6", "version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -15940,8 +16098,7 @@
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.12", "version": "2.0.12",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
"integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ=="
"dev": true
}, },
"node_modules/normalize-package-data": { "node_modules/normalize-package-data": {
"version": "2.5.0", "version": "2.5.0",
@ -16612,7 +16769,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@ -16687,7 +16843,6 @@
"version": "8.4.24", "version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -17984,7 +18139,6 @@
"version": "3.25.1", "version": "3.25.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz",
"integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==", "integrity": "sha512-tywOR+rwIt5m2ZAWSe5AIJcTat8vGlnPFAv15ycCrw33t6iFsXZ6mzHVFh2psSjxQPmI+xgzMZZizUAukBI4aQ==",
"dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
}, },
@ -18120,7 +18274,6 @@
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@ -18433,7 +18586,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -18854,6 +19006,11 @@
"resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz",
"integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==" "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g=="
}, },
"node_modules/svg-parser": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="
},
"node_modules/svg-path-bounds": { "node_modules/svg-path-bounds": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz", "resolved": "https://registry.npmjs.org/svg-path-bounds/-/svg-path-bounds-1.0.2.tgz",
@ -19742,7 +19899,6 @@
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz",
"integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -19984,7 +20140,6 @@
"version": "4.3.9", "version": "4.3.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
"dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.17.5", "esbuild": "^0.17.5",
"postcss": "^8.4.23", "postcss": "^8.4.23",
@ -20062,6 +20217,45 @@
"vite": "^2.7.0 || ^3.0.0 || ^4.0.0" "vite": "^2.7.0 || ^3.0.0 || ^4.0.0"
} }
}, },
"node_modules/vite-plugin-svgr": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-3.2.0.tgz",
"integrity": "sha512-Uvq6niTvhqJU6ga78qLKBFJSDvxWhOnyfQSoKpDPMAGxJPo5S3+9hyjExE5YDj6Lpa4uaLkGc1cBgxXov+LjSw==",
"dependencies": {
"@rollup/pluginutils": "^5.0.2",
"@svgr/core": "^7.0.0",
"@svgr/plugin-jsx": "^7.0.0"
},
"peerDependencies": {
"vite": "^2.6.0 || 3 || 4"
}
},
"node_modules/vite-plugin-svgr/node_modules/@rollup/pluginutils": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
"integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/vite-plugin-svgr/node_modules/@types/estree": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
"integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
},
"node_modules/vitest": { "node_modules/vitest": {
"version": "0.32.2", "version": "0.32.2",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.32.2.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.32.2.tgz",
@ -20461,8 +20655,7 @@
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
"dev": true
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "1.10.2", "version": "1.10.2",

View File

@ -63,7 +63,6 @@
"eslint-plugin-matrix-org": "^1.2.0", "eslint-plugin-matrix-org": "^1.2.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"postcss": "^8.4.24", "postcss": "^8.4.24",
"postcss-prune-var": "^1.1.1",
"prettier": "2.8.0", "prettier": "2.8.0",
"react-test-renderer": "^18.2.0", "react-test-renderer": "^18.2.0",
"storybook": "^7.0.22", "storybook": "^7.0.22",
@ -71,6 +70,7 @@
"typescript": "5.1.3", "typescript": "5.1.3",
"vite": "^4.3.9", "vite": "^4.3.9",
"vite-plugin-graphql-codegen": "^3.2.2", "vite-plugin-graphql-codegen": "^3.2.2",
"vite-plugin-svgr": "^3.2.0",
"vitest": "^0.32.2" "vitest": "^0.32.2"
} }
} }

View File

@ -17,9 +17,5 @@
/** @type {import('postcss-load-config').Config} */ /** @type {import('postcss-load-config').Config} */
module.exports = { module.exports = {
plugins: [ plugins: [require("tailwindcss"), require("autoprefixer")],
require("tailwindcss"),
require("autoprefixer"),
require("postcss-prune-var"),
],
}; };

View File

@ -223,6 +223,41 @@ The input/output is a string in RFC3339 format.
""" """
scalar DateTime scalar DateTime
"""
The input of the `endBrowserSession` mutation.
"""
input EndBrowserSessionInput {
"""
The ID of the session to end.
"""
browserSessionId: ID!
}
type EndBrowserSessionPayload {
"""
The status of the mutation.
"""
status: EndBrowserSessionStatus!
"""
Returns the ended session.
"""
browserSession: BrowserSession
}
"""
The status of the `endBrowserSession` mutation.
"""
enum EndBrowserSessionStatus {
"""
The session was ended.
"""
ENDED
"""
The session was not found.
"""
NOT_FOUND
}
""" """
The input of the `endCompatSession` mutation. The input of the `endCompatSession` mutation.
""" """
@ -336,6 +371,7 @@ type Mutation {
setPrimaryEmail(input: SetPrimaryEmailInput!): SetPrimaryEmailPayload! setPrimaryEmail(input: SetPrimaryEmailInput!): SetPrimaryEmailPayload!
endOauth2Session(input: EndOAuth2SessionInput!): EndOAuth2SessionPayload! endOauth2Session(input: EndOAuth2SessionInput!): EndOAuth2SessionPayload!
endCompatSession(input: EndCompatSessionInput!): EndCompatSessionPayload! endCompatSession(input: EndCompatSessionInput!): EndCompatSessionPayload!
endBrowserSession(input: EndBrowserSessionInput!): EndBrowserSessionPayload!
} }
""" """

View File

@ -12,13 +12,52 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { atom } from "jotai"; import { AnyVariables, CombinedError, OperationContext } from "@urql/core";
import { atom, WritableAtom } from "jotai";
import { useHydrateAtoms } from "jotai/utils"; import { useHydrateAtoms } from "jotai/utils";
import { atomWithQuery, clientAtom } from "jotai-urql"; import { AtomWithQuery, atomWithQuery, clientAtom } from "jotai-urql";
import type { ReactElement } from "react"; import type { ReactElement } from "react";
import { graphql } from "./gql"; import { graphql } from "./gql";
import { client } from "./graphql"; import { client } from "./graphql";
import { err, ok, Result } from "./result";
export type GqlResult<T> = Result<T, CombinedError>;
export type GqlAtom<T> = WritableAtom<
Promise<GqlResult<T>>,
[context?: Partial<OperationContext>],
void
>;
/**
* Map the result of a query atom to a new value, making it a GqlResult
*
* @param queryAtom: An atom got from atomWithQuery
* @param mapper: A function that takes the data from the query and returns a new value
*/
export const mapQueryAtom = <Data, Variables extends AnyVariables, NewData>(
queryAtom: AtomWithQuery<Data, Variables>,
mapper: (data: Data) => NewData
): GqlAtom<NewData> => {
return atom(
async (get): Promise<GqlResult<NewData>> => {
const result = await get(queryAtom);
if (result.error) {
return err(result.error);
}
if (result.data === undefined) {
throw new Error("Query result is undefined");
}
return ok(mapper(result.data));
},
(_get, set, context) => {
set(queryAtom, context);
}
);
};
export const HydrateAtoms: React.FC<{ children: ReactElement }> = ({ export const HydrateAtoms: React.FC<{ children: ReactElement }> = ({
children, children,
@ -44,13 +83,15 @@ const CURRENT_VIEWER_QUERY = graphql(/* GraphQL */ `
const currentViewerAtom = atomWithQuery({ query: CURRENT_VIEWER_QUERY }); const currentViewerAtom = atomWithQuery({ query: CURRENT_VIEWER_QUERY });
export const currentUserIdAtom = atom(async (get) => { export const currentUserIdAtom: GqlAtom<string | null> = mapQueryAtom(
const result = await get(currentViewerAtom); currentViewerAtom,
if (result.data?.viewer.__typename === "User") { (data) => {
return result.data.viewer.id; if (data.viewer.__typename === "User") {
return data.viewer.id;
}
return null;
} }
return null; );
});
const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ ` const CURRENT_VIEWER_SESSION_QUERY = graphql(/* GraphQL */ `
query CurrentViewerSessionQuery { query CurrentViewerSessionQuery {
@ -71,11 +112,11 @@ const currentViewerSessionAtom = atomWithQuery({
query: CURRENT_VIEWER_SESSION_QUERY, query: CURRENT_VIEWER_SESSION_QUERY,
}); });
export const currentBrowserSessionIdAtom = atom( export const currentBrowserSessionIdAtom: GqlAtom<string | null> = mapQueryAtom(
async (get): Promise<string | null> => { currentViewerSessionAtom,
const result = await get(currentViewerSessionAtom); (data) => {
if (result.data?.viewerSession.__typename === "BrowserSession") { if (data.viewerSession.__typename === "BrowserSession") {
return result.data.viewerSession.id; return data.viewerSession.id;
} }
return null; return null;
} }

View File

@ -13,8 +13,13 @@
// limitations under the License. // limitations under the License.
import IconWebBrowser from "@vector-im/compound-design-tokens/icons/web-browser.svg"; import IconWebBrowser from "@vector-im/compound-design-tokens/icons/web-browser.svg";
import { Body } from "@vector-im/compound-web"; import { Body, Button } from "@vector-im/compound-web";
import { atom, useSetAtom } from "jotai";
import { atomFamily } from "jotai/utils";
import { atomWithMutation } from "jotai-urql";
import { useTransition } from "react";
import { currentBrowserSessionIdAtom, currentUserIdAtom } from "../atoms";
import { FragmentType, graphql, useFragment } from "../gql"; import { FragmentType, graphql, useFragment } from "../gql";
import Block from "./Block"; import Block from "./Block";
@ -31,6 +36,30 @@ const FRAGMENT = graphql(/* GraphQL */ `
} }
`); `);
const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndBrowserSession($id: ID!) {
endBrowserSession(input: { browserSessionId: $id }) {
status
browserSession {
id
...BrowserSession_session
}
}
}
`);
const endSessionFamily = atomFamily((id: string) => {
const endSession = atomWithMutation(END_SESSION_MUTATION);
// A proxy atom which pre-sets the id variable in the mutation
const endSessionAtom = atom(
(get) => get(endSession),
(get, set) => set(endSession, { id })
);
return endSessionAtom;
});
type Props = { type Props = {
session: FragmentType<typeof FRAGMENT>; session: FragmentType<typeof FRAGMENT>;
isCurrent: boolean; isCurrent: boolean;
@ -38,10 +67,30 @@ type Props = {
const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => { const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
const data = useFragment(FRAGMENT, session); const data = useFragment(FRAGMENT, session);
const [pending, startTransition] = useTransition();
const endSession = useSetAtom(endSessionFamily(data.id));
// Pull those atoms to reset them when the current session is ended
const currentUserId = useSetAtom(currentUserIdAtom);
const currentBrowserSessionId = useSetAtom(currentBrowserSessionIdAtom);
// const lastAuthentication = data.lastAuthentication?.createdAt;
const createdAt = data.createdAt; const createdAt = data.createdAt;
const onSessionEnd = () => {
startTransition(() => {
endSession().then(() => {
if (isCurrent) {
currentBrowserSessionId({
requestPolicy: "network-only",
});
currentUserId({
requestPolicy: "network-only",
});
}
});
});
};
return ( return (
<Block className="my-4 flex items-center"> <Block className="my-4 flex items-center">
<IconWebBrowser className="mr-4 session-icon" /> <IconWebBrowser className="mr-4 session-icon" />
@ -59,15 +108,16 @@ const BrowserSession: React.FC<Props> = ({ session, isCurrent }) => {
Signed in <DateTime datetime={createdAt} /> Signed in <DateTime datetime={createdAt} />
</Body> </Body>
</div> </div>
<Body
as="a" <Button
kind="destructive"
size="sm" size="sm"
weight="medium" className="mt-2"
href="#" onClick={onSessionEnd}
className="text-critical underline hover:no-underline" disabled={pending}
> >
Sign out Sign out
</Body> </Button>
</Block> </Block>
); );
}; };

View File

@ -17,7 +17,7 @@ import { atomFamily } from "jotai/utils";
import { atomWithQuery } from "jotai-urql"; import { atomWithQuery } from "jotai-urql";
import { useTransition } from "react"; import { useTransition } from "react";
import { currentBrowserSessionIdAtom } from "../atoms"; import { currentBrowserSessionIdAtom, mapQueryAtom } from "../atoms";
import { graphql } from "../gql"; import { graphql } from "../gql";
import { PageInfo } from "../gql/graphql"; import { PageInfo } from "../gql/graphql";
import { import {
@ -25,9 +25,11 @@ import {
atomWithPagination, atomWithPagination,
Pagination, Pagination,
} from "../pagination"; } from "../pagination";
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
import BlockList from "./BlockList"; import BlockList from "./BlockList";
import BrowserSession from "./BrowserSession"; import BrowserSession from "./BrowserSession";
import GraphQLError from "./GraphQLError";
import PaginationControls from "./PaginationControls"; import PaginationControls from "./PaginationControls";
import { Title } from "./Typography"; import { Title } from "./Typography";
@ -69,17 +71,23 @@ const QUERY = graphql(/* GraphQL */ `
const currentPaginationAtom = atomForCurrentPagination(); const currentPaginationAtom = atomForCurrentPagination();
const browserSessionListFamily = atomFamily((userId: string) => { const browserSessionListFamily = atomFamily((userId: string) => {
const browserSessionList = atomWithQuery({ const browserSessionListQuery = atomWithQuery({
query: QUERY, query: QUERY,
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }), getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
}); });
const browserSessionList = mapQueryAtom(
browserSessionListQuery,
(data) => data.user?.browserSessions || null
);
return browserSessionList; return browserSessionList;
}); });
const pageInfoFamily = atomFamily((userId: string) => { const pageInfoFamily = atomFamily((userId: string) => {
const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => { const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => {
const result = await get(browserSessionListFamily(userId)); const result = await get(browserSessionListFamily(userId));
return result.data?.user?.browserSessions?.pageInfo ?? null; return (isOk(result) && unwrapOk(result)?.pageInfo) || null;
}); });
return pageInfoAtom; return pageInfoAtom;
}); });
@ -94,40 +102,43 @@ const paginationFamily = atomFamily((userId: string) => {
}); });
const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => { const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => {
const currentSessionId = useAtomValue(currentBrowserSessionIdAtom); const currentSessionIdResult = useAtomValue(currentBrowserSessionIdAtom);
const [pending, startTransition] = useTransition(); const [pending, startTransition] = useTransition();
const result = useAtomValue(browserSessionListFamily(userId)); const result = useAtomValue(browserSessionListFamily(userId));
const setPagination = useSetAtom(currentPaginationAtom); const setPagination = useSetAtom(currentPaginationAtom);
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId)); const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
if (isErr(currentSessionIdResult))
return <GraphQLError error={unwrapErr(currentSessionIdResult)} />;
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
const browserSessions = unwrapOk(result);
if (browserSessions === null) return <>Failed to load browser sessions</>;
const currentSessionId = unwrapOk(currentSessionIdResult);
const paginate = (pagination: Pagination): void => { const paginate = (pagination: Pagination): void => {
startTransition(() => { startTransition(() => {
setPagination(pagination); setPagination(pagination);
}); });
}; };
if (result.data?.user?.browserSessions) { return (
const data = result.data.user.browserSessions; <BlockList>
return ( <Title>List of browser sessions:</Title>
<BlockList> <PaginationControls
<Title>List of browser sessions:</Title> onPrev={prevPage ? (): void => paginate(prevPage) : null}
<PaginationControls onNext={nextPage ? (): void => paginate(nextPage) : null}
onPrev={prevPage ? (): void => paginate(prevPage) : null} disabled={pending}
onNext={nextPage ? (): void => paginate(nextPage) : null} />
disabled={pending} {browserSessions.edges.map((n) => (
<BrowserSession
key={n.cursor}
session={n.node}
isCurrent={n.node.id === currentSessionId}
/> />
{data.edges.map((n) => ( ))}
<BrowserSession </BlockList>
key={n.cursor} );
session={n.node}
isCurrent={n.node.id === currentSessionId}
/>
))}
</BlockList>
);
}
return <>Failed to load browser sessions</>;
}; };
export default BrowserSessionList; export default BrowserSessionList;

View File

@ -17,6 +17,7 @@ import { atomFamily } from "jotai/utils";
import { atomWithQuery } from "jotai-urql"; import { atomWithQuery } from "jotai-urql";
import { useTransition } from "react"; import { useTransition } from "react";
import { mapQueryAtom } from "../atoms";
import { graphql } from "../gql"; import { graphql } from "../gql";
import { PageInfo } from "../gql/graphql"; import { PageInfo } from "../gql/graphql";
import { import {
@ -24,9 +25,11 @@ import {
atomWithPagination, atomWithPagination,
Pagination, Pagination,
} from "../pagination"; } from "../pagination";
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
import BlockList from "./BlockList"; import BlockList from "./BlockList";
import CompatSsoLogin from "./CompatSsoLogin"; import CompatSsoLogin from "./CompatSsoLogin";
import GraphQLError from "./GraphQLError";
import PaginationControls from "./PaginationControls"; import PaginationControls from "./PaginationControls";
import { Title } from "./Typography"; import { Title } from "./Typography";
@ -67,18 +70,23 @@ const QUERY = graphql(/* GraphQL */ `
const currentPaginationAtom = atomForCurrentPagination(); const currentPaginationAtom = atomForCurrentPagination();
const compatSsoLoginListFamily = atomFamily((userId: string) => { const compatSsoLoginListFamily = atomFamily((userId: string) => {
const compatSsoLoginList = atomWithQuery({ const compatSsoLoginListQuery = atomWithQuery({
query: QUERY, query: QUERY,
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }), getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
}); });
const compatSsoLoginList = mapQueryAtom(
compatSsoLoginListQuery,
(data) => data.user?.compatSsoLogins || null
);
return compatSsoLoginList; return compatSsoLoginList;
}); });
const pageInfoFamily = atomFamily((userId: string) => { const pageInfoFamily = atomFamily((userId: string) => {
const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => { const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => {
const result = await get(compatSsoLoginListFamily(userId)); const result = await get(compatSsoLoginListFamily(userId));
return result.data?.user?.compatSsoLogins?.pageInfo ?? null; return (isOk(result) && unwrapOk(result)?.pageInfo) || null;
}); });
return pageInfoAtom; return pageInfoAtom;
@ -98,30 +106,30 @@ const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => {
const setPagination = useSetAtom(currentPaginationAtom); const setPagination = useSetAtom(currentPaginationAtom);
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId)); const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
const compatSsoLoginList = unwrapOk(result);
if (compatSsoLoginList === null)
return <>Failed to load list of compatibility sessions.</>;
const paginate = (pagination: Pagination): void => { const paginate = (pagination: Pagination): void => {
startTransition(() => { startTransition(() => {
setPagination(pagination); setPagination(pagination);
}); });
}; };
if (result.data?.user?.compatSsoLogins) { return (
const data = result.data.user.compatSsoLogins; <BlockList>
return ( <Title>List of compatibility sessions:</Title>
<BlockList> <PaginationControls
<Title>List of compatibility sessions:</Title> onPrev={prevPage ? (): void => paginate(prevPage) : null}
<PaginationControls onNext={nextPage ? (): void => paginate(nextPage) : null}
onPrev={prevPage ? (): void => paginate(prevPage) : null} disabled={pending}
onNext={nextPage ? (): void => paginate(nextPage) : null} />
disabled={pending} {compatSsoLoginList.edges.map((n) => (
/> <CompatSsoLogin login={n.node} key={n.node.id} />
{data.edges.map((n) => ( ))}
<CompatSsoLogin login={n.node} key={n.node.id} /> </BlockList>
))} );
</BlockList>
);
}
return <>Failed to load list of compatibility sessions.</>;
}; };
export default CompatSsoLoginList; export default CompatSsoLoginList;

View File

@ -0,0 +1,24 @@
// 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 { CombinedError } from "@urql/core";
import { Alert } from "@vector-im/compound-web";
const GraphQLError: React.FC<{ error: CombinedError }> = ({ error }) => (
<Alert type="critical" title={error.message}>
{error.toString()}
</Alert>
);
export default GraphQLError;

View File

@ -0,0 +1,19 @@
// 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 { Alert } from "@vector-im/compound-web";
const NotFound: React.FC = () => <Alert type="critical" title="Not found." />;
export default NotFound;

View File

@ -0,0 +1,21 @@
// 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 { Alert } from "@vector-im/compound-web";
const NotLoggedIn: React.FC = () => (
<Alert type="critical" title="You're not logged in." />
);
export default NotLoggedIn;

View File

@ -41,7 +41,7 @@ const FRAGMENT = graphql(/* GraphQL */ `
`); `);
const END_SESSION_MUTATION = graphql(/* GraphQL */ ` const END_SESSION_MUTATION = graphql(/* GraphQL */ `
mutation EndSession($id: ID!) { mutation EndOAuth2Session($id: ID!) {
endOauth2Session(input: { oauth2SessionId: $id }) { endOauth2Session(input: { oauth2SessionId: $id }) {
status status
oauth2Session { oauth2Session {

View File

@ -17,6 +17,7 @@ import { atomFamily } from "jotai/utils";
import { atomWithQuery } from "jotai-urql"; import { atomWithQuery } from "jotai-urql";
import { useTransition } from "react"; import { useTransition } from "react";
import { mapQueryAtom } from "../atoms";
import { graphql } from "../gql"; import { graphql } from "../gql";
import { PageInfo } from "../gql/graphql"; import { PageInfo } from "../gql/graphql";
import { import {
@ -24,8 +25,10 @@ import {
atomWithPagination, atomWithPagination,
Pagination, Pagination,
} from "../pagination"; } from "../pagination";
import { isErr, isOk, unwrapErr, unwrapOk } from "../result";
import BlockList from "./BlockList"; import BlockList from "./BlockList";
import GraphQLError from "./GraphQLError";
import OAuth2Session from "./OAuth2Session"; import OAuth2Session from "./OAuth2Session";
import PaginationControls from "./PaginationControls"; import PaginationControls from "./PaginationControls";
import { Title } from "./Typography"; import { Title } from "./Typography";
@ -68,18 +71,23 @@ const QUERY = graphql(/* GraphQL */ `
const currentPaginationAtom = atomForCurrentPagination(); const currentPaginationAtom = atomForCurrentPagination();
const oauth2SessionListFamily = atomFamily((userId: string) => { const oauth2SessionListFamily = atomFamily((userId: string) => {
const oauth2SessionList = atomWithQuery({ const oauth2SessionListQuery = atomWithQuery({
query: QUERY, query: QUERY,
getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }), getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }),
}); });
const oauth2SessionList = mapQueryAtom(
oauth2SessionListQuery,
(data) => data.user?.oauth2Sessions || null
);
return oauth2SessionList; return oauth2SessionList;
}); });
const pageInfoFamily = atomFamily((userId: string) => { const pageInfoFamily = atomFamily((userId: string) => {
const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => { const pageInfoAtom = atom(async (get): Promise<PageInfo | null> => {
const result = await get(oauth2SessionListFamily(userId)); const result = await get(oauth2SessionListFamily(userId));
return result.data?.user?.oauth2Sessions?.pageInfo ?? null; return (isOk(result) && unwrapOk(result)?.pageInfo) || null;
}); });
return pageInfoAtom; return pageInfoAtom;
@ -103,30 +111,30 @@ const OAuth2SessionList: React.FC<Props> = ({ userId }) => {
const setPagination = useSetAtom(currentPaginationAtom); const setPagination = useSetAtom(currentPaginationAtom);
const [prevPage, nextPage] = useAtomValue(paginationFamily(userId)); const [prevPage, nextPage] = useAtomValue(paginationFamily(userId));
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
const oauth2Sessions = unwrapOk(result);
if (oauth2Sessions === null)
return <>Failed to load OAuth 2.0 session list</>;
const paginate = (pagination: Pagination): void => { const paginate = (pagination: Pagination): void => {
startTransition(() => { startTransition(() => {
setPagination(pagination); setPagination(pagination);
}); });
}; };
if (result.data?.user?.oauth2Sessions) { return (
const data = result.data.user.oauth2Sessions; <BlockList>
return ( <Title>List of OAuth 2.0 sessions:</Title>
<BlockList> <PaginationControls
<Title>List of OAuth 2.0 sessions:</Title> onPrev={prevPage ? (): void => paginate(prevPage) : null}
<PaginationControls onNext={nextPage ? (): void => paginate(nextPage) : null}
onPrev={prevPage ? (): void => paginate(prevPage) : null} disabled={pending}
onNext={nextPage ? (): void => paginate(nextPage) : null} />
disabled={pending} {oauth2Sessions.edges.map((n) => (
/> <OAuth2Session key={n.cursor} session={n.node} />
{data.edges.map((n) => ( ))}
<OAuth2Session key={n.cursor} session={n.node} /> </BlockList>
))} );
</BlockList>
);
} else {
return <>Failed to load OAuth 2.0 session list</>;
}
}; };
export default OAuth2SessionList; export default OAuth2SessionList;

View File

@ -21,6 +21,8 @@ const documents = {
types.AddEmailDocument, types.AddEmailDocument,
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n": "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n":
types.BrowserSession_SessionFragmentDoc, types.BrowserSession_SessionFragmentDoc,
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n":
types.EndBrowserSessionDocument,
"\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n": "\n query BrowserSessionList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n browserSessions(\n first: $first\n after: $after\n last: $last\n before: $before\n ) {\n edges {\n cursor\n node {\n id\n ...BrowserSession_session\n }\n }\n\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
types.BrowserSessionListDocument, types.BrowserSessionListDocument,
"\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n ...CompatSsoLogin_session\n createdAt\n deviceId\n finishedAt\n }\n }\n": "\n fragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n ...CompatSsoLogin_session\n createdAt\n deviceId\n finishedAt\n }\n }\n":
@ -33,8 +35,8 @@ const documents = {
types.CompatSsoLoginListDocument, types.CompatSsoLoginListDocument,
"\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n": "\n fragment OAuth2Session_session on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n client {\n id\n clientId\n clientName\n clientUri\n }\n }\n":
types.OAuth2Session_SessionFragmentDoc, types.OAuth2Session_SessionFragmentDoc,
"\n mutation EndSession($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n": "\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n":
types.EndSessionDocument, types.EndOAuth2SessionDocument,
"\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\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 pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n": "\n query OAuth2SessionListQuery(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n oauth2Sessions(\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 pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n":
types.OAuth2SessionListQueryDocument, types.OAuth2SessionListQueryDocument,
"\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n": "\n fragment UserEmail_email on UserEmail {\n id\n email\n createdAt\n confirmedAt\n }\n":
@ -97,6 +99,12 @@ export function graphql(
export function graphql( export function graphql(
source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n" source: "\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"
): typeof documents["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\n }\n }\n"]; ): typeof documents["\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n lastAuthentication {\n id\n createdAt\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 mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n"
): typeof documents["\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
@ -137,8 +145,8 @@ export function graphql(
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql( export function graphql(
source: "\n mutation EndSession($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n" source: "\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n"
): typeof documents["\n mutation EndSession($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n"]; ): typeof documents["\n mutation EndOAuth2Session($id: ID!) {\n endOauth2Session(input: { oauth2SessionId: $id }) {\n status\n oauth2Session {\n id\n ...OAuth2Session_session\n }\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */

View File

@ -184,6 +184,28 @@ export type CreationEvent = {
createdAt: Scalars["DateTime"]["output"]; createdAt: Scalars["DateTime"]["output"];
}; };
/** The input of the `endBrowserSession` mutation. */
export type EndBrowserSessionInput = {
/** The ID of the session to end. */
browserSessionId: Scalars["ID"]["input"];
};
export type EndBrowserSessionPayload = {
__typename?: "EndBrowserSessionPayload";
/** Returns the ended session. */
browserSession?: Maybe<BrowserSession>;
/** The status of the mutation. */
status: EndBrowserSessionStatus;
};
/** The status of the `endBrowserSession` mutation. */
export enum EndBrowserSessionStatus {
/** The session was ended. */
Ended = "ENDED",
/** The session was not found. */
NotFound = "NOT_FOUND",
}
/** The input of the `endCompatSession` mutation. */ /** The input of the `endCompatSession` mutation. */
export type EndCompatSessionInput = { export type EndCompatSessionInput = {
/** The ID of the session to end. */ /** The ID of the session to end. */
@ -243,6 +265,7 @@ export type Mutation = {
__typename?: "Mutation"; __typename?: "Mutation";
/** Add an email address to the specified user */ /** Add an email address to the specified user */
addEmail: AddEmailPayload; addEmail: AddEmailPayload;
endBrowserSession: EndBrowserSessionPayload;
endCompatSession: EndCompatSessionPayload; endCompatSession: EndCompatSessionPayload;
endOauth2Session: EndOAuth2SessionPayload; endOauth2Session: EndOAuth2SessionPayload;
/** Remove an email address */ /** Remove an email address */
@ -260,6 +283,11 @@ export type MutationAddEmailArgs = {
input: AddEmailInput; input: AddEmailInput;
}; };
/** The mutations root of the GraphQL interface. */
export type MutationEndBrowserSessionArgs = {
input: EndBrowserSessionInput;
};
/** The mutations root of the GraphQL interface. */ /** The mutations root of the GraphQL interface. */
export type MutationEndCompatSessionArgs = { export type MutationEndCompatSessionArgs = {
input: EndCompatSessionInput; input: EndCompatSessionInput;
@ -777,6 +805,25 @@ export type BrowserSession_SessionFragment = {
} | null; } | null;
} & { " $fragmentName"?: "BrowserSession_SessionFragment" }; } & { " $fragmentName"?: "BrowserSession_SessionFragment" };
export type EndBrowserSessionMutationVariables = Exact<{
id: Scalars["ID"]["input"];
}>;
export type EndBrowserSessionMutation = {
__typename?: "Mutation";
endBrowserSession: {
__typename?: "EndBrowserSessionPayload";
status: EndBrowserSessionStatus;
browserSession?:
| ({ __typename?: "BrowserSession"; id: string } & {
" $fragmentRefs"?: {
BrowserSession_SessionFragment: BrowserSession_SessionFragment;
};
})
| null;
};
};
export type BrowserSessionListQueryVariables = Exact<{ export type BrowserSessionListQueryVariables = Exact<{
userId: Scalars["ID"]["input"]; userId: Scalars["ID"]["input"];
first?: InputMaybe<Scalars["Int"]["input"]>; first?: InputMaybe<Scalars["Int"]["input"]>;
@ -908,11 +955,11 @@ export type OAuth2Session_SessionFragment = {
}; };
} & { " $fragmentName"?: "OAuth2Session_SessionFragment" }; } & { " $fragmentName"?: "OAuth2Session_SessionFragment" };
export type EndSessionMutationVariables = Exact<{ export type EndOAuth2SessionMutationVariables = Exact<{
id: Scalars["ID"]["input"]; id: Scalars["ID"]["input"];
}>; }>;
export type EndSessionMutation = { export type EndOAuth2SessionMutation = {
__typename?: "Mutation"; __typename?: "Mutation";
endOauth2Session: { endOauth2Session: {
__typename?: "EndOAuth2SessionPayload"; __typename?: "EndOAuth2SessionPayload";
@ -1532,6 +1579,103 @@ export const AddEmailDocument = {
}, },
], ],
} as unknown as DocumentNode<AddEmailMutation, AddEmailMutationVariables>; } as unknown as DocumentNode<AddEmailMutation, AddEmailMutationVariables>;
export const EndBrowserSessionDocument = {
kind: "Document",
definitions: [
{
kind: "OperationDefinition",
operation: "mutation",
name: { kind: "Name", value: "EndBrowserSession" },
variableDefinitions: [
{
kind: "VariableDefinition",
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
type: {
kind: "NonNullType",
type: { kind: "NamedType", name: { kind: "Name", value: "ID" } },
},
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{
kind: "Field",
name: { kind: "Name", value: "endBrowserSession" },
arguments: [
{
kind: "Argument",
name: { kind: "Name", value: "input" },
value: {
kind: "ObjectValue",
fields: [
{
kind: "ObjectField",
name: { kind: "Name", value: "browserSessionId" },
value: {
kind: "Variable",
name: { kind: "Name", value: "id" },
},
},
],
},
},
],
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "status" } },
{
kind: "Field",
name: { kind: "Name", value: "browserSession" },
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{
kind: "FragmentSpread",
name: { kind: "Name", value: "BrowserSession_session" },
},
],
},
},
],
},
},
],
},
},
{
kind: "FragmentDefinition",
name: { kind: "Name", value: "BrowserSession_session" },
typeCondition: {
kind: "NamedType",
name: { kind: "Name", value: "BrowserSession" },
},
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
{
kind: "Field",
name: { kind: "Name", value: "lastAuthentication" },
selectionSet: {
kind: "SelectionSet",
selections: [
{ kind: "Field", name: { kind: "Name", value: "id" } },
{ kind: "Field", name: { kind: "Name", value: "createdAt" } },
],
},
},
],
},
},
],
} as unknown as DocumentNode<
EndBrowserSessionMutation,
EndBrowserSessionMutationVariables
>;
export const BrowserSessionListDocument = { export const BrowserSessionListDocument = {
kind: "Document", kind: "Document",
definitions: [ definitions: [
@ -2054,13 +2198,13 @@ export const CompatSsoLoginListDocument = {
CompatSsoLoginListQuery, CompatSsoLoginListQuery,
CompatSsoLoginListQueryVariables CompatSsoLoginListQueryVariables
>; >;
export const EndSessionDocument = { export const EndOAuth2SessionDocument = {
kind: "Document", kind: "Document",
definitions: [ definitions: [
{ {
kind: "OperationDefinition", kind: "OperationDefinition",
operation: "mutation", operation: "mutation",
name: { kind: "Name", value: "EndSession" }, name: { kind: "Name", value: "EndOAuth2Session" },
variableDefinitions: [ variableDefinitions: [
{ {
kind: "VariableDefinition", kind: "VariableDefinition",
@ -2151,7 +2295,10 @@ export const EndSessionDocument = {
}, },
}, },
], ],
} as unknown as DocumentNode<EndSessionMutation, EndSessionMutationVariables>; } as unknown as DocumentNode<
EndOAuth2SessionMutation,
EndOAuth2SessionMutationVariables
>;
export const OAuth2SessionListQueryDocument = { export const OAuth2SessionListQueryDocument = {
kind: "Document", kind: "Document",
definitions: [ definitions: [

View File

@ -522,6 +522,33 @@ export default {
}, },
], ],
}, },
{
kind: "OBJECT",
name: "EndBrowserSessionPayload",
fields: [
{
name: "browserSession",
type: {
kind: "OBJECT",
name: "BrowserSession",
ofType: null,
},
args: [],
},
{
name: "status",
type: {
kind: "NON_NULL",
ofType: {
kind: "SCALAR",
name: "Any",
},
},
args: [],
},
],
interfaces: [],
},
{ {
kind: "OBJECT", kind: "OBJECT",
name: "EndCompatSessionPayload", name: "EndCompatSessionPayload",
@ -637,6 +664,29 @@ export default {
}, },
], ],
}, },
{
name: "endBrowserSession",
type: {
kind: "NON_NULL",
ofType: {
kind: "OBJECT",
name: "EndBrowserSessionPayload",
ofType: null,
},
},
args: [
{
name: "input",
type: {
kind: "NON_NULL",
ofType: {
kind: "SCALAR",
name: "Any",
},
},
},
],
},
{ {
name: "endCompatSession", name: "endCompatSession",
type: { type: {

View File

@ -15,8 +15,11 @@
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { currentUserIdAtom } from "../atoms"; import { currentUserIdAtom } from "../atoms";
import GraphQLError from "../components/GraphQLError";
import NotLoggedIn from "../components/NotLoggedIn";
import UserEmailList from "../components/UserEmailList"; import UserEmailList from "../components/UserEmailList";
import UserGreeting from "../components/UserGreeting"; import UserGreeting from "../components/UserGreeting";
import { isErr, unwrapErr, unwrapOk } from "../result";
const UserAccount: React.FC<{ id: string }> = ({ id }) => { const UserAccount: React.FC<{ id: string }> = ({ id }) => {
return ( return (
@ -28,16 +31,17 @@ const UserAccount: React.FC<{ id: string }> = ({ id }) => {
}; };
const CurrentUserAccount: React.FC = () => { const CurrentUserAccount: React.FC = () => {
const userId = useAtomValue(currentUserIdAtom); const result = useAtomValue(currentUserIdAtom);
if (userId !== null) { if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
return (
<div className="w-96 mx-auto">
<UserAccount id={userId} />
</div>
);
}
return <div className="w-96 mx-auto">Not logged in.</div>; const userId = unwrapOk(result);
if (userId === null) return <NotLoggedIn />;
return (
<div className="w-96 mx-auto">
<UserAccount id={userId} />
</div>
);
}; };
export default CurrentUserAccount; export default CurrentUserAccount;

View File

@ -16,7 +16,11 @@ import { useAtomValue } from "jotai";
import { atomFamily } from "jotai/utils"; import { atomFamily } from "jotai/utils";
import { atomWithQuery } from "jotai-urql"; import { atomWithQuery } from "jotai-urql";
import { mapQueryAtom } from "../atoms";
import GraphQLError from "../components/GraphQLError";
import NotFound from "../components/NotFound";
import { graphql } from "../gql"; import { graphql } from "../gql";
import { isErr, unwrapErr, unwrapOk } from "../result";
const QUERY = graphql(/* GraphQL */ ` const QUERY = graphql(/* GraphQL */ `
query BrowserSessionQuery($id: ID!) { query BrowserSessionQuery($id: ID!) {
@ -36,26 +40,31 @@ const QUERY = graphql(/* GraphQL */ `
`); `);
const browserSessionFamily = atomFamily((id: string) => { const browserSessionFamily = atomFamily((id: string) => {
const browserSessionAtom = atomWithQuery({ const browserSessionQueryAtom = atomWithQuery({
query: QUERY, query: QUERY,
getVariables: () => ({ id }), getVariables: () => ({ id }),
}); });
const browserSessionAtom = mapQueryAtom(
browserSessionQueryAtom,
(data) => data?.browserSession
);
return browserSessionAtom; return browserSessionAtom;
}); });
const BrowserSession: React.FC<{ id: string }> = ({ id }) => { const BrowserSession: React.FC<{ id: string }> = ({ id }) => {
const result = useAtomValue(browserSessionFamily(id)); const result = useAtomValue(browserSessionFamily(id));
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
if (result.data?.browserSession) { const browserSession = unwrapOk(result);
return ( if (browserSession === null) return <NotFound />;
<pre>
<code>{JSON.stringify(result.data.browserSession, null, 2)}</code>
</pre>
);
}
return <>Failed to load browser session</>; return (
<pre>
<code>{JSON.stringify(browserSession, null, 2)}</code>
</pre>
);
}; };
export default BrowserSession; export default BrowserSession;

View File

@ -12,32 +12,34 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Alert } from "@vector-im/compound-web";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { currentUserIdAtom } from "../atoms"; import { currentUserIdAtom } from "../atoms";
import BrowserSessionList from "../components/BrowserSessionList"; import BrowserSessionList from "../components/BrowserSessionList";
import CompatSsoLoginList from "../components/CompatSsoLoginList"; import CompatSsoLoginList from "../components/CompatSsoLoginList";
import GraphQLError from "../components/GraphQLError";
import NotLoggedIn from "../components/NotLoggedIn";
import OAuth2SessionList from "../components/OAuth2SessionList"; import OAuth2SessionList from "../components/OAuth2SessionList";
import UserGreeting from "../components/UserGreeting"; import UserGreeting from "../components/UserGreeting";
import { isErr, unwrapErr, unwrapOk } from "../result";
const Home: React.FC = () => { const Home: React.FC = () => {
const currentUserId = useAtomValue(currentUserIdAtom); const result = useAtomValue(currentUserIdAtom);
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
if (currentUserId) { const currentUserId = unwrapOk(result);
return ( if (currentUserId === null) return <NotLoggedIn />;
<>
<UserGreeting userId={currentUserId} /> return (
<div className="mt-4 grid gap-1"> <>
<OAuth2SessionList userId={currentUserId} /> <UserGreeting userId={currentUserId} />
<CompatSsoLoginList userId={currentUserId} /> <div className="mt-4 grid gap-1">
<BrowserSessionList userId={currentUserId} /> <OAuth2SessionList userId={currentUserId} />
</div> <CompatSsoLoginList userId={currentUserId} />
</> <BrowserSessionList userId={currentUserId} />
); </div>
} else { </>
return <Alert type="critical" title="You're not logged in." />; );
}
}; };
export default Home; export default Home;

View File

@ -16,7 +16,11 @@ import { useAtomValue } from "jotai";
import { atomFamily } from "jotai/utils"; import { atomFamily } from "jotai/utils";
import { atomWithQuery } from "jotai-urql"; import { atomWithQuery } from "jotai-urql";
import { mapQueryAtom } from "../atoms";
import GraphQLError from "../components/GraphQLError";
import NotFound from "../components/NotFound";
import { graphql } from "../gql"; import { graphql } from "../gql";
import { isErr, unwrapErr, unwrapOk } from "../result";
const QUERY = graphql(/* GraphQL */ ` const QUERY = graphql(/* GraphQL */ `
query OAuth2ClientQuery($id: ID!) { query OAuth2ClientQuery($id: ID!) {
@ -33,26 +37,31 @@ const QUERY = graphql(/* GraphQL */ `
`); `);
const oauth2ClientFamily = atomFamily((id: string) => { const oauth2ClientFamily = atomFamily((id: string) => {
const oauth2ClientAtom = atomWithQuery({ const oauth2ClientQueryAtom = atomWithQuery({
query: QUERY, query: QUERY,
getVariables: () => ({ id }), getVariables: () => ({ id }),
}); });
const oauth2ClientAtom = mapQueryAtom(
oauth2ClientQueryAtom,
(data) => data?.oauth2Client
);
return oauth2ClientAtom; return oauth2ClientAtom;
}); });
const OAuth2Client: React.FC<{ id: string }> = ({ id }) => { const OAuth2Client: React.FC<{ id: string }> = ({ id }) => {
const result = useAtomValue(oauth2ClientFamily(id)); const result = useAtomValue(oauth2ClientFamily(id));
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
if (result.data?.oauth2Client) { const oauth2Client = unwrapOk(result);
return ( if (oauth2Client === null) return <NotFound />;
<pre>
<code>{JSON.stringify(result.data.oauth2Client, null, 2)}</code>
</pre>
);
}
return <>Failed to load OAuth2 client</>; return (
<pre>
<code>{JSON.stringify(oauth2Client, null, 2)}</code>
</pre>
);
}; };
export default OAuth2Client; export default OAuth2Client;

61
frontend/src/result.ts Normal file
View File

@ -0,0 +1,61 @@
// 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 RESULT = Symbol("Result");
const ERR = Symbol("Err");
const OK = Symbol("Ok");
/**
* An `Ok` is a type that represents a successful result.
*/
export type Ok<T> = {
[RESULT]: typeof OK;
[OK]: T;
};
/**
* An `Err` is a type that represents an error.
*/
export type Err<E> = {
[RESULT]: typeof ERR;
[ERR]: E;
};
/**
* A `Result` is a type that represents either an `Ok` or an `Err`.
*/
export type Result<T, E> = Ok<T> | Err<E>;
// Construct an `Ok`
export const ok = <T>(data: T): Ok<T> => ({ [RESULT]: OK, [OK]: data });
// Construct an `Err`
export const err = <E>(error: E): Err<E> => ({
[RESULT]: ERR,
[ERR]: error,
});
// Check if a `Result` is an `Ok`
export const isOk = <T, E>(result: Result<T, E>): result is Ok<T> =>
result[RESULT] === OK;
// Check if a `Result` is an `Err`
export const isErr = <T, E>(result: Result<T, E>): result is Err<E> =>
result[RESULT] === ERR;
// Extract the data from an `Ok`
export const unwrapOk = <T>(result: Ok<T>): T => result[OK];
// Extract the error from an `Err`
export const unwrapErr = <E>(result: Err<E>): E => result[ERR];

View File

@ -16,8 +16,9 @@
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import codegen from "vite-plugin-graphql-codegen"; import codegen from "vite-plugin-graphql-codegen";
import svgr from "vite-plugin-svgr";
export default defineConfig({ export default defineConfig((env) => ({
base: "/app/", base: "/app/",
build: { build: {
manifest: true, manifest: true,
@ -29,11 +30,50 @@ export default defineConfig({
react({ react({
babel: { babel: {
plugins: [ plugins: [
"jotai/babel/plugin-react-refresh", [
"jotai/babel/plugin-debug-label", "jotai/babel/plugin-react-refresh",
{
customAtomNames: [
"mapQueryAtom",
"atomWithPagination",
"atomWithCurrentPagination",
],
},
],
[
"jotai/babel/plugin-debug-label",
{
customAtomNames: [
"mapQueryAtom",
"atomWithPagination",
"atomWithCurrentPagination",
],
},
],
], ],
}, },
}), }),
svgr({
exportAsDefault: true,
esbuildOptions: {
// This makes sure we're using the same JSX runtime as React itself
jsx: "automatic",
jsxDev: env.mode === "development",
},
svgrOptions: {
// Using 1em in order to make SVG size inherits from text size.
icon: "1em",
svgProps: {
// Adding a class in case we want to add global overrides, but one
// should probably stick to using CSS modules most of the time
className: "cpd-icon",
},
},
}),
], ],
server: { server: {
proxy: { proxy: {
@ -50,4 +90,4 @@ export default defineConfig({
all: true, all: true,
}, },
}, },
}); }));