update web, fix integration issues

This commit is contained in:
41666 2022-01-30 16:14:52 -05:00
parent 2fb721078e
commit e162096c03
30 changed files with 476 additions and 2574 deletions

View file

@ -15,6 +15,10 @@ module.exports = {
: [match.loader.include];
match.loader.include = [...include, ...includePaths];
}
webpackConfig.resolve.fallback = {
crypto: false,
};
return webpackConfig;
},
},

View file

@ -8,7 +8,7 @@ export const getDefaultApiUrl = memoizeOne.default((host: string) => {
/^stage\.roleypoly\.com$/.test(host)
) {
return 'https://api-stage.roleypoly.com';
} else if (/\blocalhost|127\.0\.0\.1\b/.test(host)) {
} else if (/\blocalhost|127\.0\.0\.1|172.21.92\b/.test(host)) {
return 'http://localhost:6609';
} else {
return 'https://api-prod.roleypoly.com';

View file

@ -1,6 +1,7 @@
import { GuildSlug, PresentableGuild } from '@roleypoly/types';
import React from 'react';
import { useApiContext } from '../api/ApiContext';
import { useAuthedFetch } from '../session/AuthedFetchContext';
import { useSessionContext } from '../session/SessionContext';
const CACHE_HOLD_TIME = 2 * 60 * 1000; // 2 minutes
@ -29,7 +30,8 @@ export const GuildContext = React.createContext<GuildContextT>({
export const useGuildContext = () => React.useContext(GuildContext);
export const GuildProvider = (props: { children: React.ReactNode }) => {
const { session, authedFetch } = useSessionContext();
const { session } = useSessionContext();
const { authedFetch } = useAuthedFetch();
const { fetch } = useApiContext();
const guildContextValue: GuildContextT = {

View file

@ -0,0 +1,34 @@
import React from 'react';
import { useApiContext } from '../api/ApiContext';
import { useSessionContext } from './SessionContext';
type AuthedFetchContextT = {
authedFetch: (url: string, options?: RequestInit) => Promise<Response>;
};
export const AuthedFetchContext = React.createContext<AuthedFetchContextT>({
authedFetch: () => Promise.reject(new Error('AuthedFetchContext not initialized')),
});
export const useAuthedFetch = () => React.useContext(AuthedFetchContext);
export const AuthedFetchProvider = (props: { children: React.ReactNode }) => {
const { fetch } = useApiContext();
const { sessionID } = useSessionContext();
const authedFetch = (url: string, options?: RequestInit) => {
return fetch(url, {
...options,
headers: {
...options?.headers,
Authorization: sessionID ? `Bearer ${sessionID}` : undefined,
},
});
};
return (
<AuthedFetchContext.Provider value={{ authedFetch }}>
{props.children}
</AuthedFetchContext.Provider>
);
};

View file

@ -2,12 +2,6 @@ import { SessionData } from '@roleypoly/types';
import * as React from 'react';
import { useApiContext } from '../api/ApiContext';
enum SessionState {
NoAuth,
HalfAuth,
FullAuth,
}
type SavedSession = {
sessionID: SessionData['sessionID'];
@ -18,10 +12,8 @@ type SavedSession = {
};
type SessionContextT = {
setupSession: (sessionID: string) => void;
authedFetch: (url: string, opts?: RequestInit) => Promise<Response>;
setupSession: (sessionID: string | null) => void;
isAuthenticated: boolean;
sessionState: SessionState;
sessionID?: SessionData['sessionID'];
session: {
user?: SessionData['user'];
@ -30,149 +22,125 @@ type SessionContextT = {
};
const SessionContext = React.createContext<SessionContextT>({
sessionState: SessionState.NoAuth,
sessionID: undefined,
isAuthenticated: false,
session: {
user: undefined,
guilds: undefined,
},
isAuthenticated: false,
setupSession: () => {},
authedFetch: async () => {
return new Response();
},
});
export const useSessionContext = () => React.useContext(SessionContext);
export const SessionContextProvider = (props: { children: React.ReactNode }) => {
const { fetch } = useApiContext();
const [sessionID, setSessionID] =
React.useState<SessionContextT['sessionID']>(undefined);
const [sessionState, setSessionState] = React.useState<SessionState>(
SessionState.NoAuth
);
const [session, setSession] = React.useState<SessionContextT['session']>({
user: undefined,
guilds: undefined,
const [locked, setLock] = React.useState(false);
const [session, setSession] = React.useState<SessionContextT>({
sessionID: undefined,
session: {
user: undefined,
guilds: undefined,
},
isAuthenticated: false,
setupSession: (key: string | null) => {
if (key) {
saveSessionKey(key);
setSession({
...session,
sessionID: key,
});
} else {
deleteSessionKey();
deleteSessionData();
setSession({
...session,
sessionID: undefined,
isAuthenticated: false,
});
}
},
});
const [lock, setLock] = React.useState(false);
// Possible flows:
/*
if no key, check if key available in LS
No session:
no key
isAuth = false
Half session:
have key
lock = true
isAuth = false
fetch cached in SS _OR_ syncSession()
lock = false
Full session
have session
isAuth = true
*/
const sessionContextValue: SessionContextT = {
sessionID,
session,
sessionState,
isAuthenticated: sessionState === SessionState.FullAuth,
setupSession: async (newID: string) => {
setSessionID(newID);
setSessionState(SessionState.HalfAuth);
saveSessionKey(newID);
},
authedFetch: async (url: string, init?: RequestInit): Promise<Response> => {
if (sessionID) {
init = {
...init,
headers: {
...init?.headers,
authorization: `Bearer ${sessionID}`,
},
};
React.useEffect(() => {
const fetchSession = async (sessionID: string): Promise<ServerSession | null> => {
const sessionResponse = await fetch('/auth/session', {
headers: {
Authorization: `Bearer ${sessionID}`,
},
});
if (sessionResponse.status !== 200) {
return null;
}
return fetch(url, init);
},
};
const { guilds, user }: ServerSession = await sessionResponse.json();
return {
sessionID,
guilds,
user,
};
};
const { setupSession, authedFetch } = sessionContextValue;
// Local storage sync on NoAuth
React.useEffect(() => {
if (!sessionID) {
const storedKey = getSessionKey();
if (!storedKey) {
return;
}
setupSession(storedKey);
}
}, [sessionID, setupSession]);
// Sync session data on HalfAuth
React.useEffect(() => {
if (lock) {
console.warn('hit syncSession lock');
if (locked) {
console.warn('Session locked, skipping update');
return;
}
if (!session.sessionID) {
const sessionKey = getSessionKey();
if (sessionKey) {
session.setupSession(sessionKey);
}
}
if (sessionState === SessionState.HalfAuth) {
setLock(true);
// Use cached session
const storedData = getSessionData();
if (storedData && storedData?.sessionID === sessionID) {
setSession(storedData.session);
setSessionState(SessionState.FullAuth);
setLock(false);
return;
if (session.sessionID && !session.session.user) {
// Lets see if session is in session storage...
const sessionData = getSessionData();
if (sessionData) {
setSession({
...session,
isAuthenticated: true,
session: {
user: sessionData.session.user,
guilds: sessionData.session.guilds,
},
});
}
// If no cached session, let's grab it from server
const syncSession = async () => {
try {
const serverSession = await fetchSession(authedFetch);
if (!serverSession) {
// Not found, lets reset.
deleteSessionKey();
setSessionID(undefined);
setSessionState(SessionState.NoAuth);
// If not, lets fetch it from the server
setLock(true);
fetchSession(session.sessionID)
.then((sessionData) => {
if (sessionData) {
setSession({
...session,
isAuthenticated: true,
session: {
user: sessionData.user,
guilds: sessionData.guilds,
},
});
saveSessionData({
sessionID: session.sessionID!,
session: {
user: sessionData.user,
guilds: sessionData.guilds,
},
});
} else {
session.setupSession(null);
setLock(false);
return;
}
const newSession = {
user: serverSession.user,
guilds: serverSession.guilds,
};
saveSessionData({ sessionID: sessionID || '', session: newSession });
setSession(newSession);
setSessionState(SessionState.FullAuth);
})
.catch((e) => {
console.error(e);
session.setupSession(null);
setLock(false);
} catch (e) {
console.error('syncSession failed', e);
deleteSessionKey();
setTimeout(() => setLock(false), 1000); // Unlock after 1s to prevent loop flood
}
};
syncSession();
});
}
}, [sessionState, sessionID, authedFetch, lock]);
}, [session, locked, setLock, fetch]);
return (
<SessionContext.Provider value={sessionContextValue}>
{props.children}
</SessionContext.Provider>
<SessionContext.Provider value={session}>{props.children}</SessionContext.Provider>
);
};
@ -181,23 +149,9 @@ const deleteSessionKey = () => localStorage.removeItem('rp_session_key');
const getSessionKey = () => localStorage.getItem('rp_session_key');
type ServerSession = Omit<Omit<SessionData, 'tokens'>, 'flags'>;
const fetchSession = async (
authedFetch: SessionContextT['authedFetch']
): Promise<ServerSession | null> => {
const sessionResponse = await authedFetch('/auth/session');
if (sessionResponse.status !== 200) {
return null;
}
const { sessionID, guilds, user }: ServerSession = await sessionResponse.json();
return {
sessionID,
guilds,
user,
};
};
const saveSessionData = (data: SavedSession) =>
sessionStorage.setItem('rp_session_data', JSON.stringify(data));
const getSessionData = (): SavedSession | null =>
JSON.parse(sessionStorage.getItem('rp_session_data') || 'null');
const deleteSessionData = () => localStorage.removeItem('rp_session_data');

View file

@ -6,6 +6,7 @@ import { ApiContextProvider } from './contexts/api/ApiContext';
import { AppShellPropsProvider } from './contexts/app-shell/AppShellContext';
import { GuildProvider } from './contexts/guild/GuildContext';
import { RecentGuildsProvider } from './contexts/recent-guilds/RecentGuildsContext';
import { AuthedFetchProvider } from './contexts/session/AuthedFetchContext';
import { SessionContextProvider } from './contexts/session/SessionContext';
const ProviderProvider = (props: {
@ -24,6 +25,7 @@ ReactDOM.render(
providerChain={[
ApiContextProvider,
SessionContextProvider,
AuthedFetchProvider,
RecentGuildsProvider,
AppShellPropsProvider,
BreakpointsProvider,

View file

@ -10,6 +10,7 @@ import * as React from 'react';
import { useAppShellProps } from '../contexts/app-shell/AppShellContext';
import { useGuildContext } from '../contexts/guild/GuildContext';
import { useRecentGuilds } from '../contexts/recent-guilds/RecentGuildsContext';
import { useAuthedFetch } from '../contexts/session/AuthedFetchContext';
import { useSessionContext } from '../contexts/session/SessionContext';
import { Title } from '../utils/metaTitle';
@ -20,10 +21,11 @@ type EditorProps = {
const Editor = (props: EditorProps) => {
const { serverID } = props;
const { session, authedFetch, isAuthenticated } = useSessionContext();
const { session, isAuthenticated } = useSessionContext();
const { authedFetch } = useAuthedFetch();
const { pushRecentGuild } = useRecentGuilds();
const appShellProps = useAppShellProps();
const { getFullGuild } = useGuildContext();
const { getFullGuild, uncacheGuild } = useGuildContext();
const [guild, setGuild] = React.useState<PresentableGuild | null | false>(null);
const [pending, setPending] = React.useState(false);
@ -96,6 +98,7 @@ const Editor = (props: EditorProps) => {
if (response.status === 200) {
setGuild(guild);
uncacheGuild(serverID);
navigate(`/s/${props.serverID}`);
}

View file

@ -11,10 +11,12 @@ import React from 'react';
import { useAppShellProps } from '../../contexts/app-shell/AppShellContext';
import { useGuildContext } from '../../contexts/guild/GuildContext';
import { useRecentGuilds } from '../../contexts/recent-guilds/RecentGuildsContext';
import { useAuthedFetch } from '../../contexts/session/AuthedFetchContext';
import { useSessionContext } from '../../contexts/session/SessionContext';
const AccessControlPage = (props: { serverID: string; path: string }) => {
const { session, isAuthenticated, authedFetch } = useSessionContext();
const { session, isAuthenticated } = useSessionContext();
const { authedFetch } = useAuthedFetch();
const { pushRecentGuild } = useRecentGuilds();
const { getFullGuild, uncacheGuild } = useGuildContext();
const appShellProps = useAppShellProps();

View file

@ -7,6 +7,7 @@ import * as React from 'react';
import { useAppShellProps } from '../contexts/app-shell/AppShellContext';
import { useGuildContext } from '../contexts/guild/GuildContext';
import { useRecentGuilds } from '../contexts/recent-guilds/RecentGuildsContext';
import { useAuthedFetch } from '../contexts/session/AuthedFetchContext';
import { useSessionContext } from '../contexts/session/SessionContext';
import { Title } from '../utils/metaTitle';
import { makeRoleTransactions } from '../utils/roleTransactions';
@ -17,7 +18,8 @@ type PickerProps = {
};
const Picker = (props: PickerProps) => {
const { session, authedFetch, isAuthenticated } = useSessionContext();
const { session, isAuthenticated } = useSessionContext();
const { authedFetch } = useAuthedFetch();
const { pushRecentGuild } = useRecentGuilds();
const appShellProps = useAppShellProps();
const { getFullGuild, uncacheGuild } = useGuildContext();