@@ -1,5 +0,0 @@
|
||||
const genAPIKey = () => {
|
||||
return [...Array(30)]
|
||||
.map((e) => ((Math.random() * 36) | 0).toString(36))
|
||||
.join('');
|
||||
};
|
||||
105
server/src/api/record.ts
Normal file
105
server/src/api/record.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { readFile, readFiles } from "../workflow-management/storage";
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { requireAPIKey } from "../middlewares/api";
|
||||
export const router = Router();
|
||||
|
||||
const formatRecording = (recordingData: any) => {
|
||||
const recordingMeta = recordingData.recording_meta;
|
||||
const workflow = recordingData.recording.workflow || [];
|
||||
const firstWorkflowStep = workflow[0]?.where?.url || '';
|
||||
|
||||
const inputParameters = [
|
||||
{
|
||||
type: "string",
|
||||
name: "originUrl",
|
||||
label: "Origin URL",
|
||||
required: true,
|
||||
defaultValue: firstWorkflowStep,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
id: recordingMeta.id,
|
||||
name: recordingMeta.name,
|
||||
createdAt: new Date(recordingMeta.create_date).getTime(),
|
||||
inputParameters,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
router.get("/api/robots", requireAPIKey, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const fileContents = await readFiles('./../storage/recordings/');
|
||||
|
||||
const formattedRecordings = fileContents.map((fileContent: string) => {
|
||||
const recordingData = JSON.parse(fileContent);
|
||||
return formatRecording(recordingData);
|
||||
});
|
||||
|
||||
const response = {
|
||||
statusCode: 200,
|
||||
messageCode: "success",
|
||||
robots: {
|
||||
totalCount: formattedRecordings.length,
|
||||
items: formattedRecordings,
|
||||
},
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching recordings:", error);
|
||||
res.status(500).json({
|
||||
statusCode: 500,
|
||||
messageCode: "error",
|
||||
message: "Failed to retrieve recordings",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const formatRecordingById = (recordingData: any) => {
|
||||
const recordingMeta = recordingData.recording_meta;
|
||||
const workflow = recordingData.recording.workflow || [];
|
||||
const firstWorkflowStep = workflow[0]?.where?.url || '';
|
||||
|
||||
const inputParameters = [
|
||||
{
|
||||
type: "string",
|
||||
name: "originUrl",
|
||||
label: "Origin URL",
|
||||
required: true,
|
||||
defaultValue: firstWorkflowStep,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
id: recordingMeta.id,
|
||||
name: recordingMeta.name,
|
||||
createdAt: new Date(recordingMeta.create_date).getTime(),
|
||||
inputParameters,
|
||||
};
|
||||
};
|
||||
|
||||
router.get("/api/robots/:fileName", requireAPIKey, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const fileContent = await readFile(`./../storage/recordings/${req.params.fileName}.waw.json`);
|
||||
|
||||
const recordingData = JSON.parse(fileContent);
|
||||
const formattedRecording = formatRecordingById(recordingData);
|
||||
|
||||
const response = {
|
||||
statusCode: 200,
|
||||
messageCode: "success",
|
||||
robot: formattedRecording,
|
||||
};
|
||||
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching recording:", error);
|
||||
res.status(404).json({
|
||||
statusCode: 404,
|
||||
messageCode: "not_found",
|
||||
message: `Recording with name "${req.params.fileName}" not found.`,
|
||||
});
|
||||
}
|
||||
});
|
||||
15
server/src/middlewares/api.ts
Normal file
15
server/src/middlewares/api.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Request, Response } from "express";
|
||||
import User from "../models/User";
|
||||
|
||||
export const requireAPIKey = async (req: Request, res: Response, next: any) => {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
if (!apiKey) {
|
||||
return res.status(401).json({ error: "API key is missing" });
|
||||
}
|
||||
const user = await User.findOne({ where: { api_key: apiKey } });
|
||||
if (!user) {
|
||||
return res.status(403).json({ error: "Invalid API key" });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
};
|
||||
@@ -5,6 +5,7 @@ interface UserAttributes {
|
||||
id: number;
|
||||
email: string;
|
||||
password: string;
|
||||
api_key?: string | null;
|
||||
}
|
||||
|
||||
// Optional fields for creating a new user
|
||||
@@ -14,6 +15,7 @@ class User extends Model<UserAttributes, UserCreationAttributes> implements User
|
||||
public id!: number;
|
||||
public email!: string;
|
||||
public password!: string;
|
||||
public api_key!: string | null;
|
||||
}
|
||||
|
||||
User.init(
|
||||
@@ -35,6 +37,10 @@ User.init(
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
api_key: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
|
||||
40
server/src/routes/api.ts
Normal file
40
server/src/routes/api.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Router, Request, Response } from 'express';
|
||||
import { genAPIKey } from '../utils/api';
|
||||
import User from '../models/User';
|
||||
|
||||
export const router = Router();
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
user?: { id: string };
|
||||
}
|
||||
|
||||
router.get('/generate-api-key', async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ ok: false, error: 'Unauthorized' });
|
||||
}
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
attributes: { exclude: ['password'] },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
|
||||
if (user.api_key) {
|
||||
return res.status(400).json({ message: 'API key already exists' });
|
||||
}
|
||||
|
||||
const apiKey = genAPIKey();
|
||||
|
||||
user.api_key = apiKey;
|
||||
await user.save();
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'API key generated successfully',
|
||||
api_key: apiKey,
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: 'Error generating API key', error });
|
||||
}
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import User from '../models/User';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { hashPassword, comparePassword } from '../utils/auth';
|
||||
import { requireSignIn } from '../middlewares/auth';
|
||||
import { genAPIKey } from '../utils/api';
|
||||
export const router = Router();
|
||||
|
||||
interface AuthenticatedRequest extends Request {
|
||||
@@ -88,4 +89,35 @@ router.get('/current-user', requireSignIn, async (req: AuthenticatedRequest, res
|
||||
console.error('Error in current-user route:', error);
|
||||
return res.status(500).json({ ok: false, error: `Could not fetch current user: ${error.message}` });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/generate-api-key', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
||||
try {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({ ok: false, error: 'Unauthorized' });
|
||||
}
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
attributes: { exclude: ['password'] },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: 'User not found' });
|
||||
}
|
||||
|
||||
if (user.api_key) {
|
||||
return res.status(400).json({ message: 'API key already exists' });
|
||||
}
|
||||
|
||||
const apiKey = genAPIKey();
|
||||
|
||||
user.api_key = apiKey;
|
||||
await user.save();
|
||||
|
||||
return res.status(200).json({
|
||||
message: 'API key generated successfully',
|
||||
api_key: apiKey,
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(500).json({ message: 'Error generating API key', error });
|
||||
}
|
||||
});
|
||||
|
||||
3
server/src/utils/api.ts
Normal file
3
server/src/utils/api.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const genAPIKey = (): string => {
|
||||
return [...Array(30)].map(() => ((Math.random() * 36) | 0).toString(36)).join('');
|
||||
};
|
||||
@@ -122,4 +122,3 @@ export const processGoogleSheetUpdates = async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ interface InterpretationLogProps {
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, setIsOpen }) => {
|
||||
export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, setIsOpen }) => {
|
||||
const [log, setLog] = useState<string>('');
|
||||
const [selectedOption, setSelectedOption] = useState<string>('10');
|
||||
const [customValue, setCustomValue] = useState('');
|
||||
@@ -66,12 +66,12 @@ export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, se
|
||||
setLog((prevState) =>
|
||||
prevState + '\n' + '---------- Serializable output data received ----------' + '\n'
|
||||
+ JSON.stringify(data, null, 2) + '\n' + '--------------------------------------------------');
|
||||
|
||||
|
||||
// Set table data
|
||||
if (Array.isArray(data)) {
|
||||
setTableData(data);
|
||||
}
|
||||
|
||||
|
||||
scrollLogToBottom();
|
||||
}, [log, scrollLogToBottom]);
|
||||
|
||||
@@ -104,7 +104,7 @@ export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, se
|
||||
|
||||
// Extract columns dynamically from the first item of tableData
|
||||
const columns = tableData.length > 0 ? Object.keys(tableData[0]) : [];
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
|
||||
@@ -7,7 +7,7 @@ import { TabPanel, TabContext } from "@mui/lab";
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import ImageIcon from '@mui/icons-material/Image';
|
||||
import ArticleIcon from '@mui/icons-material/Article';
|
||||
import {Buffer} from 'buffer';
|
||||
import { Buffer } from 'buffer';
|
||||
import { useEffect } from "react";
|
||||
import AssignmentIcon from '@mui/icons-material/Assignment';
|
||||
|
||||
@@ -19,7 +19,7 @@ interface RunContentProps {
|
||||
abortRunHandler: () => void,
|
||||
}
|
||||
|
||||
export const RunContent = ({row, currentLog, interpretationInProgress, logEndRef, abortRunHandler}: RunContentProps) => {
|
||||
export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRef, abortRunHandler }: RunContentProps) => {
|
||||
const [tab, setTab] = React.useState<string>('log');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,141 +29,142 @@ export const RunContent = ({row, currentLog, interpretationInProgress, logEndRef
|
||||
return (
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<TabContext value={tab}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={tab} onChange={(e, newTab) => setTab(newTab)} aria-label="run-content-tabs">
|
||||
<Tab label="Log" value='log' />
|
||||
<Tab label="Input" value='input' />
|
||||
<Tab label="Output" value='output' />
|
||||
</Tabs>
|
||||
</Box>
|
||||
<TabPanel value='log'>
|
||||
<Box sx={{ margin: 1,
|
||||
background: '#19171c',
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'scroll',
|
||||
width: '700px',
|
||||
height: 'fit-content',
|
||||
maxHeight: '450px',
|
||||
}}>
|
||||
<div>
|
||||
<Highlight className="javascript">
|
||||
{interpretationInProgress ? currentLog : row.log}
|
||||
</Highlight>
|
||||
<div style={{ float:"left", clear: "both" }}
|
||||
ref={logEndRef}/>
|
||||
</div>
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={tab} onChange={(e, newTab) => setTab(newTab)} aria-label="run-content-tabs">
|
||||
<Tab label="Log" value='log' />
|
||||
<Tab label="Input" value='input' />
|
||||
<Tab label="Output" value='output' />
|
||||
</Tabs>
|
||||
</Box>
|
||||
{interpretationInProgress ? <Button
|
||||
color="error"
|
||||
onClick={abortRunHandler}
|
||||
>
|
||||
Stop
|
||||
</Button> : null}
|
||||
</TabPanel>
|
||||
<TabPanel value='input' sx={{width: '700px'}}>
|
||||
<Typography variant='h6' sx={{display:'flex', alignItems:'center'}}>
|
||||
<SettingsIcon sx={{marginRight: '15px'}}/>
|
||||
Interpreter settings
|
||||
</Typography>
|
||||
{
|
||||
Object.keys(row.interpreterSettings).map((setting, index) => {
|
||||
if (setting === 'params') {
|
||||
return (
|
||||
<div key={`settings-${setting}-${index}`}>
|
||||
<Typography variant='h6' sx={{display:'flex', alignItems:'center'}} key={`setting-${index}`}>
|
||||
<AssignmentIcon sx={{marginRight: '15px'}}/>
|
||||
Recording parameters
|
||||
</Typography>
|
||||
{
|
||||
Object.keys(row.interpreterSettings.params).map((param, index) => {
|
||||
return (
|
||||
<Typography key={`recording-params-item-${index}`} sx={{margin: '10px'}}>
|
||||
{/*@ts-ignore*/}
|
||||
{param}: {row.interpreterSettings.params[param].toString()}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Typography key={`interpreter-settings-item-${index}`} sx={{margin: '10px'}}>
|
||||
{/*@ts-ignore*/}
|
||||
{setting}: {row.interpreterSettings[setting].toString()}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel value='output' sx={{width: '700px'}}>
|
||||
{ !row || !row.serializableOutput || !row.binaryOutput
|
||||
|| (Object.keys(row.serializableOutput).length === 0 && Object.keys(row.binaryOutput).length === 0)
|
||||
? <Typography>The output is empty.</Typography> : null }
|
||||
|
||||
{row.serializableOutput &&
|
||||
Object.keys(row.serializableOutput).length !== 0 &&
|
||||
<div>
|
||||
<Typography variant='h6' sx={{display:'flex', alignItems:'center'}}>
|
||||
<ArticleIcon sx={{marginRight: '15px'}}/>
|
||||
Serializable output</Typography>
|
||||
{ Object.keys(row.serializableOutput).map((key) => {
|
||||
return (
|
||||
<div key={`number-of-serializable-output-${key}`}>
|
||||
<Typography>
|
||||
{key}:
|
||||
<a href={`data:application/json;utf8,${JSON.stringify(row.serializableOutput[key], null, 2)}`}
|
||||
download={key} style={{margin:'10px'}}>Download</a>
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
width: 'fit-content',
|
||||
background: 'rgba(0,0,0,0.06)',
|
||||
maxHeight: '300px',
|
||||
overflow: 'scroll',
|
||||
}}>
|
||||
<pre key={`serializable-output-${key}`}>
|
||||
{row.serializableOutput[key] ? JSON.stringify(row.serializableOutput[key], null, 2)
|
||||
: 'The output is empty.'}
|
||||
</pre>
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
{row.binaryOutput
|
||||
&& Object.keys(row.binaryOutput).length !== 0 &&
|
||||
<div>
|
||||
<Typography variant='h6' sx={{display:'flex', alignItems:'center'}}>
|
||||
<ImageIcon sx={{marginRight:'15px'}}/>
|
||||
Binary output</Typography>
|
||||
{ Object.keys(row.binaryOutput).map((key) => {
|
||||
try {
|
||||
const binaryBuffer = JSON.parse(row.binaryOutput[key].data);
|
||||
const b64 = Buffer.from(binaryBuffer.data).toString('base64');
|
||||
<TabPanel value='log'>
|
||||
<Box sx={{
|
||||
margin: 1,
|
||||
background: '#19171c',
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'scroll',
|
||||
width: '700px',
|
||||
height: 'fit-content',
|
||||
maxHeight: '450px',
|
||||
}}>
|
||||
<div>
|
||||
<Highlight className="javascript">
|
||||
{interpretationInProgress ? currentLog : row.log}
|
||||
</Highlight>
|
||||
<div style={{ float: "left", clear: "both" }}
|
||||
ref={logEndRef} />
|
||||
</div>
|
||||
</Box>
|
||||
{interpretationInProgress ? <Button
|
||||
color="error"
|
||||
onClick={abortRunHandler}
|
||||
>
|
||||
Stop
|
||||
</Button> : null}
|
||||
</TabPanel>
|
||||
<TabPanel value='input' sx={{ width: '700px' }}>
|
||||
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<SettingsIcon sx={{ marginRight: '15px' }} />
|
||||
Interpreter settings
|
||||
</Typography>
|
||||
{
|
||||
Object.keys(row.interpreterSettings).map((setting, index) => {
|
||||
if (setting === 'params') {
|
||||
return (
|
||||
<Box key={`number-of-binary-output-${key}`} sx={{
|
||||
width: 'max-content',
|
||||
}}>
|
||||
<Typography key={`binary-output-key-${key}`}>
|
||||
{key}:
|
||||
<a href={`data:${row.binaryOutput[key].mimetype};base64,${b64}`}
|
||||
download={key} style={{margin:'10px'}}>Download</a>
|
||||
<div key={`settings-${setting}-${index}`}>
|
||||
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }} key={`setting-${index}`}>
|
||||
<AssignmentIcon sx={{ marginRight: '15px' }} />
|
||||
Recording parameters
|
||||
</Typography>
|
||||
<img key={`image-${key}`} src={`data:${row.binaryOutput[key].mimetype};base64,${b64}`}
|
||||
alt={key} height='auto' width='700px'/>
|
||||
</Box>
|
||||
{
|
||||
Object.keys(row.interpreterSettings.params).map((param, index) => {
|
||||
return (
|
||||
<Typography key={`recording-params-item-${index}`} sx={{ margin: '10px' }}>
|
||||
{/*@ts-ignore*/}
|
||||
{param}: {row.interpreterSettings.params[param].toString()}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return <Typography key={`number-of-binary-output-${key}`}>
|
||||
{key}: The image failed to render
|
||||
</Typography>
|
||||
}
|
||||
return (
|
||||
<Typography key={`interpreter-settings-item-${index}`} sx={{ margin: '10px' }}>
|
||||
{/*@ts-ignore*/}
|
||||
{setting}: {row.interpreterSettings[setting].toString()}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel value='output' sx={{ width: '700px' }}>
|
||||
{!row || !row.serializableOutput || !row.binaryOutput
|
||||
|| (Object.keys(row.serializableOutput).length === 0 && Object.keys(row.binaryOutput).length === 0)
|
||||
? <Typography>The output is empty.</Typography> : null}
|
||||
|
||||
{row.serializableOutput &&
|
||||
Object.keys(row.serializableOutput).length !== 0 &&
|
||||
<div>
|
||||
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<ArticleIcon sx={{ marginRight: '15px' }} />
|
||||
Serializable output</Typography>
|
||||
{Object.keys(row.serializableOutput).map((key) => {
|
||||
return (
|
||||
<div key={`number-of-serializable-output-${key}`}>
|
||||
<Typography>
|
||||
{key}:
|
||||
<a href={`data:application/json;utf8,${JSON.stringify(row.serializableOutput[key], null, 2)}`}
|
||||
download={key} style={{ margin: '10px' }}>Download</a>
|
||||
</Typography>
|
||||
<Box sx={{
|
||||
width: 'fit-content',
|
||||
background: 'rgba(0,0,0,0.06)',
|
||||
maxHeight: '300px',
|
||||
overflow: 'scroll',
|
||||
}}>
|
||||
<pre key={`serializable-output-${key}`}>
|
||||
{row.serializableOutput[key] ? JSON.stringify(row.serializableOutput[key], null, 2)
|
||||
: 'The output is empty.'}
|
||||
</pre>
|
||||
</Box>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
</div>
|
||||
}
|
||||
{row.binaryOutput
|
||||
&& Object.keys(row.binaryOutput).length !== 0 &&
|
||||
<div>
|
||||
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<ImageIcon sx={{ marginRight: '15px' }} />
|
||||
Binary output</Typography>
|
||||
{Object.keys(row.binaryOutput).map((key) => {
|
||||
try {
|
||||
const binaryBuffer = JSON.parse(row.binaryOutput[key].data);
|
||||
const b64 = Buffer.from(binaryBuffer.data).toString('base64');
|
||||
return (
|
||||
<Box key={`number-of-binary-output-${key}`} sx={{
|
||||
width: 'max-content',
|
||||
}}>
|
||||
<Typography key={`binary-output-key-${key}`}>
|
||||
{key}:
|
||||
<a href={`data:${row.binaryOutput[key].mimetype};base64,${b64}`}
|
||||
download={key} style={{ margin: '10px' }}>Download</a>
|
||||
</Typography>
|
||||
<img key={`image-${key}`} src={`data:${row.binaryOutput[key].mimetype};base64,${b64}`}
|
||||
alt={key} height='auto' width='700px' />
|
||||
</Box>
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return <Typography key={`number-of-binary-output-${key}`}>
|
||||
{key}: The image failed to render
|
||||
</Typography>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
</TabContext>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ const initialState = {
|
||||
|
||||
const AuthContext = createContext<{
|
||||
state: InitialStateType;
|
||||
dispatch: any;
|
||||
dispatch: React.Dispatch<ActionType>;
|
||||
}>({
|
||||
state: initialState,
|
||||
dispatch: () => null,
|
||||
@@ -46,38 +46,34 @@ const reducer = (state: InitialStateType, action: ActionType) => {
|
||||
|
||||
const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const navigate = useNavigate();
|
||||
axios.defaults.withCredentials = true;
|
||||
|
||||
// get user info from local storage
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'LOGIN',
|
||||
payload: JSON.parse(window.localStorage.getItem('user') || 'null'),
|
||||
});
|
||||
const storedUser = window.localStorage.getItem('user');
|
||||
if (storedUser) {
|
||||
dispatch({ type: 'LOGIN', payload: JSON.parse(storedUser) });
|
||||
}
|
||||
}, []);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
function (response) {
|
||||
// any status code that lies within the range of 2XX causes this function to trigger
|
||||
return response;
|
||||
},
|
||||
function (error) {
|
||||
// any status codes that fall outside the range of 2XX cause this function to trigger
|
||||
let res = error.response;
|
||||
const res = error.response;
|
||||
if (res.status === 401 && res.config && !res.config.__isRetryRequest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get('http://localhost:8080/auth/logout')
|
||||
.then((data) => {
|
||||
.then(() => {
|
||||
console.log('/401 error > logout');
|
||||
dispatch({ type: 'LOGOUT' });
|
||||
window.localStorage.removeItem('user');
|
||||
navigate('/login'); // Replace router.push with navigate
|
||||
navigate('/login');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('AXIOS INTERCEPTORS ERROR:', err);
|
||||
console.error('AXIOS INTERCEPTORS ERROR:', err);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
@@ -86,16 +82,12 @@ const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||
}
|
||||
);
|
||||
|
||||
// csrf - include tokens in the axios header every time a request is made
|
||||
useEffect(() => {
|
||||
const getCsrfToken = async () => {
|
||||
try {
|
||||
const { data } = await axios.get('http://localhost:8080/csrf-token');
|
||||
console.log('CSRF Token Response:', data);
|
||||
if (data && data.csrfToken) {
|
||||
if (data.csrfToken) {
|
||||
(axios.defaults.headers as any)['X-CSRF-TOKEN'] = data.csrfToken;
|
||||
} else {
|
||||
console.error('CSRF token not found in the response');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching CSRF token:', error);
|
||||
@@ -105,8 +97,10 @@ const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ state, dispatch }}>{children}</AuthContext.Provider>
|
||||
<AuthContext.Provider value={{ state, dispatch }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { AuthContext, AuthProvider };
|
||||
export { AuthContext, AuthProvider };
|
||||
Reference in New Issue
Block a user