auth and socket connections linear and slow progress but cleaner?

This commit is contained in:
talksik
2022-05-13 06:20:08 -05:00
parent 5eb57726ab
commit fc9efed3e2
9 changed files with 535 additions and 220 deletions
+91
View File
@@ -0,0 +1,91 @@
import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios';
import CreateLineRequest from '@nirvana/core/requests/createLine.request';
import GetUserLinesResponse from '@nirvana/core/responses/getUserLines.response';
import { Line } from '@nirvana/core/models/line.model';
import LoginResponse from '../../../core/responses/login.response';
import MasterLineData from '@nirvana/core/models/masterLineData.model';
import NirvanaResponse from '../../../core/responses/nirvanaResponse';
import { User } from '@nirvana/core/models';
import UserDetailsResponse from '../../../core/responses/userDetails.response';
import UserSearchResponse from '../../../core/responses/userSearch.response';
// export const localHost = process.env.REACT_APP_API_DOMAIN;
export const localHost = 'http://localhost:5000/api';
export default class NirvanaApi {
// auth token from google that our backend will use
static _jwtToken?: string;
static async fetch<T>(url: string, method: Method, privateRoute = false, body: object = null) {
// use the auth token if it's a private route
// error if no auth token and it's a private route
// throw error and show message on anything that is an error from the backend
const fullUrl = localHost + url;
let res;
if (privateRoute && !this._jwtToken) throw Error('No jwt token available!');
if (privateRoute && this._jwtToken) {
res = await fetch(fullUrl, {
method: method,
headers: {
Authorization: this._jwtToken,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : null,
});
} else {
res = await fetch(fullUrl);
}
if (!res.ok) {
if (res.status === 401) throw new Error('You are not authorized here');
throw new Error('Something went wrong');
}
return await res.json();
}
}
export async function serverCheck(): Promise<void> {
return await NirvanaApi.fetch(`/status`, 'GET', false);
}
export async function login(reqLoginTokens: {
accessToken: string;
idToken: string;
}): Promise<LoginResponse> {
return await NirvanaApi.fetch(
`/user/login?access_token=${reqLoginTokens.accessToken}&id_token=${reqLoginTokens.idToken}`,
'GET',
false,
);
}
export async function authCheck(): Promise<void> {
return await NirvanaApi.fetch(`/user/authcheck`, 'GET', true);
}
export async function getUserDetails(): Promise<UserDetailsResponse> {
return await NirvanaApi.fetch(`/user`, 'GET', true);
}
export async function userSearch(searchQuery: string): Promise<UserSearchResponse> {
return await NirvanaApi.fetch(`/search/users?query=${searchQuery}`, 'GET', true);
}
export async function getUserLines(): Promise<NirvanaResponse<GetUserLinesResponse>> {
return await NirvanaApi.fetch<NirvanaResponse<GetUserLinesResponse>>(`/lines`, 'GET', true);
}
export async function getDmByUserId(otherUserId: string): Promise<Line> {
return await NirvanaApi.fetch(`/lines/dm/${otherUserId}`, 'GET', true);
}
export async function createLine(request: CreateLineRequest): Promise<NirvanaResponse<Line>> {
return await NirvanaApi.fetch(`/lines`, 'POST', true, request);
}
@@ -1,18 +1,53 @@
import React, { useContext, useState, useEffect } from 'react';
import { User } from '@nirvana/core/models/user.model';
import { useAsyncFn } from 'react-use';
import NirvanaApi, { getUserDetails } from '../api/NirvanaApi';
import toast from 'react-hot-toast';
import { useCallback } from 'react';
interface IAuthProvider {
user: User;
user?: User;
jwtToken?: string;
setJwtToken?: (jwtToken: string) => void;
}
const AuthContext = React.createContext<IAuthProvider>({
user: null,
});
const AuthContext = React.createContext<IAuthProvider>({});
export function AuthProvider({ children }: { children: React.ReactNode }) {
if (true) return <></>;
const [jwtToken, setJwtToken] = useState<string>();
const [userFetchState, fetchUser] = useAsyncFn(getUserDetails);
return <AuthContext.Provider value={{}}>{children}</AuthContext.Provider>;
useEffect(() => {
if (jwtToken) {
console.warn(jwtToken);
// ?should this be set here? what's a better way of
NirvanaApi._jwtToken = jwtToken;
fetchUser()
.then((response) => {
toast.success('authenticated user');
})
.catch(() => {
toast.error('problem in authenticating jwt token');
});
}
}, [jwtToken, fetchUser]);
const handleSetJwtToken = useCallback(
(newJwtToken: string) => {
setJwtToken(newJwtToken);
},
[setJwtToken],
);
return (
<AuthContext.Provider
value={{ user: userFetchState.value?.user, setJwtToken: handleSetJwtToken, jwtToken }}
>
{children}
</AuthContext.Provider>
);
}
export default function useAuth() {
@@ -1,5 +1,7 @@
import React, { useState, useEffect, useContext } from 'react';
import { Socket } from 'socket.io-client';
import toast from 'react-hot-toast';
import { io, Socket } from 'socket.io-client';
import useAuth from './AuthProvider';
interface ISocketProvider {
$ws: Socket;
@@ -10,9 +12,17 @@ const SocketContext = React.createContext<ISocketProvider>({
});
export function SocketProvider({ children }: { children: React.ReactNode }) {
const { jwtToken } = useAuth();
const [$ws, set$ws] = useState<Socket>(null);
useEffect(() => {
if (!jwtToken) {
toast.error('no jwt token!!!');
return;
}
const socketConnection = io('http://localhost:5000', {
query: { token: jwtToken },
transports: ['websocket'],
@@ -21,10 +31,45 @@ export function SocketProvider({ children }: { children: React.ReactNode }) {
reconnection: false, // ! TESTING THE PROBLEM WITH RECONNECTION CLIENT LISTENERS NOT ACTIVATING
});
set$ws(socketConnection);
}, [set$ws]);
socketConnection.on('connect', () => {
toast.success('sockets connected');
});
if (!$ws) return <span>please wait for a solid connection</span>;
socketConnection.io.on('close', () => {
console.error(
'SOCKET | there was a problem with your app...connection closed likely due to idling or manual disconnect',
);
toast.error('sorry...this is our bad...please refresh with cmd + r');
});
// client-side errors
socketConnection.on('connect_error', (err) => {
console.error(`SOCKETS | ${err.message}`);
toast.error('sorry...this is our bad...please refresh with cmd + r');
});
set$ws(socketConnection);
return () => {
socketConnection.disconnect();
};
}, [set$ws, jwtToken]);
if (!$ws) {
return (
<div className="h-screen w-screen justify-center items-center bg-white flex flex-col">
<span className="text-gray-400">
{
"Please wait for a solid connection. If that doesn't work, please click below or restart the application."
}
</span>
<button onClick={() => console.warn('NOT IMPLEMENTED...manual reconnect button')}>
Reconnect
</button>
</div>
);
}
return <SocketContext.Provider value={{ $ws }}>{children}</SocketContext.Provider>;
}
+20 -5
View File
@@ -1,14 +1,29 @@
import React from 'react';
import { Toaster } from 'react-hot-toast';
import { AuthProvider } from '../providers/AuthProvider';
import { ElectronProvider } from '../providers/ElectronProvider';
import SocketProvider from '../providers/SocketProvider';
import { SocketProvider } from '../providers/SocketProvider';
import ProtectedRoute from './protected/ProtectedRoute';
export default function ElectronApp() {
return (
<ElectronProvider>
<SocketProvider>
<div> this is the bottom of the tree</div>
</SocketProvider>
<AuthProvider>
<ProtectedRoute>
<SocketProvider>yo its connected dawg</SocketProvider>
</ProtectedRoute>
</AuthProvider>
<Toaster
position={'bottom-right'}
toastOptions={{
style: {
borderRadius: '10px',
background: '#333',
color: '#fff',
},
}}
/>
</ElectronProvider>
);
}
@@ -1,9 +1,11 @@
import React from 'react';
import useAuth from '../../providers/AuthProvider';
import Login from './login/Login';
export default function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user } = useAuth();
if (user) return;
return <>hey</>;
if (!user) return <Login />;
return <>{children}</>;
}
@@ -1,16 +1,18 @@
import Channels, { STORE_ITEMS } from '../../electron/constants';
import { useEffect, useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import React, { useEffect, useState } from 'react';
import { $jwtToken } from '../../controller/recoil';
import { FcGoogle } from 'react-icons/fc';
import Logo from '../../components/Logo';
import { useLogin } from '../../controller/index';
// import Logo from '../../components/Logo';
import { login } from '../../../api/NirvanaApi';
import { useAsyncFn } from 'react-use';
import Channels from '../../../electron/constants';
import useAuth from '../../../providers/AuthProvider';
export default function Login() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const { setJwtToken } = useAuth();
const setJwtToken = useSetRecoilState($jwtToken);
const [loginState, loginApiHandler] = useAsyncFn(login);
const [isLoading, setIsLoading] = useState<boolean>(false);
useEffect(() => {
window.electronAPI.once(
@@ -19,15 +21,16 @@ export default function Login() {
console.log('got tokens', tokens);
setIsLoading(true);
// todo: implement refresh token procedure in api layer by sending refresh_token and such
const loginResponse = await mutateAsync({
const loginResponse = await loginApiHandler({
accessToken: tokens.access_token,
idToken: tokens.id_token,
});
const { jwtToken, userDetails } = loginResponse;
setJwtToken(jwtToken);
setIsLoading(false);
},
);
@@ -35,8 +38,9 @@ export default function Login() {
// return () => {
// window.electronAPI.removeAllListeners(Channels.GOOGLE_AUTH_TOKENS);
// };
}, []);
}, [loginApiHandler, setJwtToken]);
// take user to browser to complete authentication
const continueAuth = () => {
setIsLoading(true);
@@ -50,7 +54,7 @@ export default function Login() {
h-screen w-screen
bg-zinc-700"
>
<Logo className="scale-50" />
{/* <Logo className="scale-50" /> */}
{/* ! TESTING PURPOSES */}
<div className={'text-white flex flex-col gap-5'}>