auth and socket connections linear and slow progress but cleaner?
This commit is contained in:
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
Reference in New Issue
Block a user