From 71102cfb3b7384adff7093bb63c2de8a0eff45b5 Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 4 Sep 2020 16:17:48 +0200 Subject: [PATCH] =?UTF-8?q?Conservation=20de=20l'=C3=A9tat=20connect=C3=A9?= =?UTF-8?q?=20entre=202=20rafraichissement=20de=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'état de connexion est conservé dans le sessionStorage et réutilisé par défaut lors du rafraichissement de la page. Si une erreur 401 survient lors d'un appel à l'API alors l'utilisateur est redirigé vers la page d'accueil. --- client/src/components/App.tsx | 86 +++++++++++++------ .../DashboardPage/WorkgroupsPanel.tsx | 9 +- client/src/components/HomePage/HomePage.tsx | 7 +- client/src/components/LogoutPage.tsx | 15 ++++ client/src/components/Navbar.tsx | 4 +- client/src/gql/client.tsx | 60 ------------- client/src/hooks/useLoggedIn.tsx | 22 ++++- client/src/index.tsx | 5 +- client/src/util/apollo.ts | 74 ++++++++++++++++ internal/voter/manager.go | 4 - 10 files changed, 177 insertions(+), 109 deletions(-) create mode 100644 client/src/components/LogoutPage.tsx delete mode 100644 client/src/gql/client.tsx create mode 100644 client/src/util/apollo.ts diff --git a/client/src/components/App.tsx b/client/src/components/App.tsx index f0b3e67..adfb1ae 100644 --- a/client/src/components/App.tsx +++ b/client/src/components/App.tsx @@ -1,4 +1,4 @@ -import React, { FunctionComponent, useState } from 'react'; +import React, { FunctionComponent, useState, useEffect } from 'react'; import { BrowserRouter, Route, Redirect, Switch } from "react-router-dom"; import { HomePage } from './HomePage/HomePage'; import { ProfilePage } from './ProfilePage/ProfilePage'; @@ -6,44 +6,76 @@ import { WorkgroupPage } from './WorkgroupPage/WorkgroupPage'; import { DecisionSupportFilePage } from './DecisionSupportFilePage/DecisionSupportFilePage'; import { DashboardPage } from './DashboardPage/DashboardPage'; import { useUserProfile } from '../gql/queries/profile'; -import { LoggedInContext } from '../hooks/useLoggedIn'; +import { LoggedInContext, getSavedLoggedIn, saveLoggedIn } from '../hooks/useLoggedIn'; import { PrivateRoute } from './PrivateRoute'; import { useKonamiCode } from '../hooks/useKonamiCode'; import { Modal } from './Modal'; +import { createClient } from '../util/apollo'; +import { ApolloProvider } from '@apollo/client'; +import { LogoutPage } from './LogoutPage'; export interface AppProps { } + + export const App: FunctionComponent = () => { - const { user } = useUserProfile(); + const [ loggedIn, setLoggedIn ] = useState(getSavedLoggedIn()); + + const client = createClient((loggedIn) => { + setLoggedIn(loggedIn); + }); + + useEffect(() => { + saveLoggedIn(loggedIn); + }, [loggedIn]); const [ showBoneyM, setShowBoneyM ] = useState(false); useKonamiCode(() => setShowBoneyM(true)); return ( - - - - - - - - - } /> - - - { - showBoneyM ? - setShowBoneyM(false)}> - - : - null - } - + + + + + + + + + + + + } /> + + + { + showBoneyM ? + setShowBoneyM(false)}> + + : + null + } + + ); -} \ No newline at end of file +} + +interface UserSessionCheckProps { + setLoggedIn: (boolean) => void +} + +const UserSessionCheck: FunctionComponent = ({ setLoggedIn }) => { + const { user, loading } = useUserProfile(); + + useEffect(() => { + if (loading) return; + setLoggedIn(user.id !== ''); + }, [user]); + + return null; +}; \ No newline at end of file diff --git a/client/src/components/DashboardPage/WorkgroupsPanel.tsx b/client/src/components/DashboardPage/WorkgroupsPanel.tsx index c7ea0d1..5bb9684 100644 --- a/client/src/components/DashboardPage/WorkgroupsPanel.tsx +++ b/client/src/components/DashboardPage/WorkgroupsPanel.tsx @@ -1,10 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { } from 'react'; import { Workgroup, inWorkgroup } from '../../types/workgroup'; -import { User } from '../../types/user'; -import { Link } from 'react-router-dom'; -import { useWorkgroupsQuery, useWorkgroups } from '../../gql/queries/workgroups'; -import { useUserProfileQuery, useUserProfile } from '../../gql/queries/profile'; -import { WithLoader } from '../WithLoader'; +import { useWorkgroups } from '../../gql/queries/workgroups'; +import { useUserProfile } from '../../gql/queries/profile'; import { ItemPanel, Item } from './ItemPanel'; export function WorkgroupsPanel() { diff --git a/client/src/components/HomePage/HomePage.tsx b/client/src/components/HomePage/HomePage.tsx index f455d4e..a3af9ff 100644 --- a/client/src/components/HomePage/HomePage.tsx +++ b/client/src/components/HomePage/HomePage.tsx @@ -3,14 +3,15 @@ import { Page } from '../Page'; import { WelcomeContent } from './WelcomeContent'; import { useUserProfile } from '../../gql/queries/profile'; import { useHistory } from 'react-router'; +import { useLoggedIn } from '../../hooks/useLoggedIn'; export function HomePage() { - const { user } = useUserProfile(); + const loggedIn = useLoggedIn(); const history = useHistory(); useEffect(() => { - if (user.id !== '') history.push('/dashboard'); - }, [user.id]) + if (loggedIn) history.push('/dashboard'); + }, [loggedIn]) return ( diff --git a/client/src/components/LogoutPage.tsx b/client/src/components/LogoutPage.tsx new file mode 100644 index 0000000..56cd70a --- /dev/null +++ b/client/src/components/LogoutPage.tsx @@ -0,0 +1,15 @@ +import React, { FunctionComponent, useEffect } from "react"; +import { saveLoggedIn } from "../hooks/useLoggedIn"; +import { Config } from "../config"; + +export interface LogoutPageProps { + +} + +export const LogoutPage: FunctionComponent = () => { + useEffect(() => { + saveLoggedIn(false); + window.location.replace(Config.logoutURL); + }, []); + return null; +}; \ No newline at end of file diff --git a/client/src/components/Navbar.tsx b/client/src/components/Navbar.tsx index a5d38fe..036e8e4 100644 --- a/client/src/components/Navbar.tsx +++ b/client/src/components/Navbar.tsx @@ -44,11 +44,11 @@ export function Navbar() { Mon profil - + - + : diff --git a/client/src/gql/client.tsx b/client/src/gql/client.tsx deleted file mode 100644 index 259ca55..0000000 --- a/client/src/gql/client.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client'; -import { Config } from '../config'; -import { WebSocketLink } from "@apollo/client/link/ws"; -import { RetryLink } from "@apollo/client/link/retry"; -import { SubscriptionClient } from "subscriptions-transport-ws"; -import { User } from '../types/user'; - -const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, { - reconnect: true, -}); - -const link = new RetryLink({attempts: {max: 2}}).split( - (operation) => operation.operationName === 'subscription', - new WebSocketLink(subscriptionClient), - new HttpLink({ uri: Config.graphQLEndpoint, credentials: 'include' }) -); - -const cache = new InMemoryCache({ - typePolicies: { - Workgroup: { - fields: { - members: { - merge: mergeArrayByField("id"), - } - } - } - } -}); - -export const client = new ApolloClient({ - cache: cache, - link: link, -}); - -function mergeArrayByField(fieldName: string) { - return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => { - - const merged: any[] = existing ? existing.slice(0) : []; - - const objectFieldToIndex: Record = Object.create(null); - if (existing) { - existing.forEach((obj, index) => { - objectFieldToIndex[readField(fieldName, obj)] = index; - }); - } - - incoming.forEach(obj => { - const field = readField(fieldName, obj); - const index = objectFieldToIndex[field]; - if (typeof index === "number") { - merged[index] = mergeObjects(merged[index], obj); - } else { - objectFieldToIndex[name] = merged.length; - merged.push(obj); - } - }); - - return merged; - } -} \ No newline at end of file diff --git a/client/src/hooks/useLoggedIn.tsx b/client/src/hooks/useLoggedIn.tsx index 8c92e4d..2764c04 100644 --- a/client/src/hooks/useLoggedIn.tsx +++ b/client/src/hooks/useLoggedIn.tsx @@ -1,7 +1,23 @@ -import React, { useState, useContext } from "react"; +import React, { useContext, useEffect } from "react"; -export const LoggedInContext = React.createContext(false); +const LOGGED_IN_KEY = 'loggedIn'; + +export const LoggedInContext = React.createContext(getSavedLoggedIn()); export const useLoggedIn = () => { return useContext(LoggedInContext); -}; \ No newline at end of file +}; + +export function saveLoggedIn(loggedIn: boolean) { + console.log("saveLoggedIn", JSON.stringify(loggedIn)) + window.sessionStorage.setItem(LOGGED_IN_KEY, JSON.stringify(loggedIn)); +} + +export function getSavedLoggedIn(): boolean { + try { + const loggedIn = JSON.parse(window.sessionStorage.getItem(LOGGED_IN_KEY)); + return !!loggedIn; + } catch(err) { + return false; + } +} \ No newline at end of file diff --git a/client/src/index.tsx b/client/src/index.tsx index cee36c1..03c2010 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -2,7 +2,6 @@ import './sass/_all.scss'; import React from 'react'; import ReactDOM from 'react-dom'; import { App } from './components/App'; -import { client } from './gql/client'; import '@fortawesome/fontawesome-free/js/fontawesome' import '@fortawesome/fontawesome-free/js/solid' @@ -12,8 +11,6 @@ import './resources/favicon.png'; import { ApolloProvider } from '@apollo/client'; ReactDOM.render( - - - , + , document.getElementById('app') ); diff --git a/client/src/util/apollo.ts b/client/src/util/apollo.ts new file mode 100644 index 0000000..519dee5 --- /dev/null +++ b/client/src/util/apollo.ts @@ -0,0 +1,74 @@ + +import { ApolloClient, InMemoryCache, HttpLink, from } from '@apollo/client'; +import { Config } from '../config'; +import { WebSocketLink } from "@apollo/client/link/ws"; +import { RetryLink } from "@apollo/client/link/retry"; +import { onError } from "@apollo/client/link/error"; +import { SubscriptionClient } from "subscriptions-transport-ws"; +import { User } from '../types/user'; + +export function createClient(setLoggedIn: (boolean) => void) { + const subscriptionClient = new SubscriptionClient(Config.subscriptionEndpoint, { + reconnect: true, + }); + + const errorLink = onError(({ operation }) => { + const { response } = operation.getContext(); + if (response.status === 401) setLoggedIn(false); + }); + + const retryLink = new RetryLink({attempts: {max: 2}}).split( + (operation) => operation.operationName === 'subscription', + new WebSocketLink(subscriptionClient), + new HttpLink({ + uri: Config.graphQLEndpoint, + credentials: 'include', + }) + ); + + const cache = new InMemoryCache({ + typePolicies: { + Workgroup: { + fields: { + members: { + merge: mergeArrayByField("id"), + } + } + } + } + }); + + return new ApolloClient({ + cache: cache, + link: from([ + errorLink, + retryLink + ]), + }); +} + +export function mergeArrayByField(fieldName: string) { + return (existing: T[] = [], incoming: T[], { readField, mergeObjects }) => { + const merged: any[] = existing ? existing.slice(0) : []; + + const objectFieldToIndex: Record = Object.create(null); + if (existing) { + existing.forEach((obj, index) => { + objectFieldToIndex[readField(fieldName, obj)] = index; + }); + } + + incoming.forEach(obj => { + const field = readField(fieldName, obj); + const index = objectFieldToIndex[field]; + if (typeof index === "number") { + merged[index] = mergeObjects(merged[index], obj); + } else { + objectFieldToIndex[name] = merged.length; + merged.push(obj); + } + }); + + return merged; + } +} \ No newline at end of file diff --git a/internal/voter/manager.go b/internal/voter/manager.go index 0e99ca9..cf56bd7 100644 --- a/internal/voter/manager.go +++ b/internal/voter/manager.go @@ -3,8 +3,6 @@ package voter import ( "context" - "github.com/davecgh/go-spew/spew" - "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" ) @@ -40,8 +38,6 @@ func (m *Manager) Authorized(ctx context.Context, subject interface{}, obj inter decisions = append(decisions, dec) } - spew.Dump(decisions) - result, err := m.strategy(ctx, decisions) if err != nil { return Deny, errors.WithStack(err)