I am trying to logout the user in my react app automatically. I am receiving token from backend API that expires after 1 hour. I am storing the received token in my local storage as soon as the user login. My protected route 'DefaultLayout.js' stores the logout function and is rendered after the user logs in successfully. Currently I am serving my app on localhost but I want to implement this functionality on production also. The problem I am facing:
Implementing automatic logout not working even after one hour of login session.
Here is my code snippets:
DefaultLAyout.js
class DefaultLayout extends Component {
loading = () => <div className="animated fadeIn pt-1 text-center">Loading...</div>
componentDidMount(){
let userInfo = LocalData.getLocalData('currentUser', 'all');
console.log(userInfo);
if (userInfo.success === false && userInfo.message === 'Failed to authenticate user' && userInfo.responseStatus === 403){
localStorage.clear();
this.props.history.push('/login')
}
else if(userInfo.userToken === null){
localStorage.clear();
this.props.history.push('/login')
}
}
signOut(e) {
//e.preventDefault();
localStorage.clear();
this.props.history.push('/login')
}
render() {
//console.log(this.props)
return (
<div className="app">
<AppHeader fixed>
<Suspense fallback={this.loading()}>
<DefaultHeader onLogout={e=>this.signOut(e)}/>
</Suspense>
</AppHeader>
<div className="app-body">
<AppSidebar fixed display="lg">
<AppSidebarHeader />
<AppSidebarForm />
<Suspense>
{LocalData.getLocalData("currentUser", 'isLogin') === true ? <AppSidebarNav navConfig={navigationProtected} {...this.props} router={router}/> : <AppSidebarNav navConfig={navigation} {...this.props} router={router}/>}
</Suspense>
<AppSidebarFooter />
<AppSidebarMinimizer />
</AppSidebar>
...... }
}
Login.js
class Login extends Component {
// _isMounted = false;
constructor(){
super();
this.state = {
isLogged: false,
email: '',
password: '',
isSuccess: true,
isLoggedInSuccess: false,
}
}
onChange = (e) => {
e.preventDefault();
this.setState({ [e.target.name]: e.target.value });
}
loginUser = async (e) => {
const { email, password} = this.state;
e.preventDefault();
await axios.post(GlobalVar.BASE_URL+'api/user/login', { email, password })
.then(res => {
const loginResponse = res;
console.log(loginResponse);
if(loginResponse.status === 200 && loginResponse.data.success === true){
LocalData.setLocalData("currentUser", loginResponse);
this.setState({
isLoggedInSuccess: true,
isSuccess: res.data.success
}, () => this.props.history.push('/'));
}
else{
const error = new Error('Invalid Login!');
this.setState({
isLoggedInSuccess: false
});
throw error;
}
})
.catch(err => {
console.log(err);
this.setState({isLoggedInSuccess: false, isSuccess: false});
});
}
...}
setLocalstorage.js
export default function setLocalData(key, value)
{
var responseStatus = false;
switch (key)
{
case 'currentUser':
const initialState = {
isLogin: true,
userToken: value.data.data,
responseStatus: value.status,
message: value.data.message,
success: value.data.success
};
localStorage.setItem(key, btoa(JSON.stringify(initialState)));
responseStatus = true;
break;
default:
responseStatus = false;
break;
}
return responseStatus;
}
getLocalStorage.js
export default function getLocalData(key, type='all')
{
var responseObject = null;
try
{
if(localStorage.getItem(key))
{
var response;
response = JSON.parse(atob(localStorage.getItem(key)));
switch (type)
{
case 'all':
responseObject = (response) ? response : null;
break;
case 'isLogin':
responseObject = (response.isLogin);
break;
case 'successMsg':
responseObject = (response.message);
break;
case 'getToken':
responseObject = (response) ? response.userToken : null;
break;
case 'getResponseStatus':
responseObject = (response) ? response.responseStatus : null;
break;
case 'getSuccessStatus':
responseObject = (response) ? response.success : false;
break;
case 'getFormData':
responseObject = (response) ? response : null;
break;
default:
responseObject = null;
break;
}
}
}
catch (e)
{
responseObject = null;
}
return responseObject;
}
Data response from LocalStorage(consoled in DefaultLayout.js)
{
isLogin: true
message: "User successfully logged in"
responseStatus: 200
success: true
userToken: "eyJhbGciOiJ..."
__proto__: Object
}
I have tried lots of workaround but nothing seems to solve my problem. Kindly help out this soon.
//you can implement auto-logout by the following code
let hours = 1;
let saved = localStorage.getItem(....);
if (saved && (new Date().getTime() - saved > hours * 60 * 60 * 1000)) {
localStorage.clear()
}
// Increase expiration time after save
localStorage.setItem('saved', new Date().getTime())
// you can check logout status while making any call, if its more than an hour you can clear localstorage & redirect
Related
I have a component like this -->
<form onSubmit={verifyOTP}>
....
.....
<div className="auth-btn-wrap">
<button disabled={isLoading ? true : false} type="submit" className="btn btrimary">
{isLoading ? (<CircularProgress />) : (<>Verify OTP</>)}</button>
{isLoading ? null : (<Link onClick={resendOTP}>Resend</Link>)}
</div>
<div id="recaptcha-container" />
</form>
verifyOTP function looks like this -->
const verifyOTP = (e: React.SyntheticEvent) => {
e.preventDefault();
if (window.confirmationResult) {
window.confirmationResult
.confirm(otp)
.then(async () => {
dispatch(signupStartStart({ user: { ...user, otp, otpConfirm: window.confirmationResult }, history }));
})
.catch((e: any) => {
console.error(e);
snack.error("Invalid OTP");
});
} else {
window.location.reload();
}
};
In my user saga file, action signupStartStart looks like this -->
{.......
.......
const result = yield call(sendOTPWithFb, {
phoneNumber: user.countryCode + user.phoneNumber, containerName: "recaptcha-container"
});
yield put (setLoading ({loading: false}));
if (result) {
yield put(showVerifyOTPSuccess());
snack.success('An OTP has been sent to you mobile');
} else {
snack.error("Unable to send OTP");
}
}
The function that sends OTP is this -->
export const sendOTPWithFb = async (data: any): Promise<boolean> => {
const { phoneNumber, containerName } = data
try {
const appVerifier = new FirebaseTypes.auth.RecaptchaVerifier(
containerName,
{
size: "invisible",
}
);
const confirmationResult = await firebase
.auth()
.signInWithPhoneNumber(`${phoneNumber}`, appVerifier);
window.confirmationResult = confirmationResult;
return true;
} catch (error) {
console.error(error);
return false;
}
};
Whenever I click on Resend OTP button, it gives me error that --->
"reCAPTCHA has already been rendered in this element"
PLease let me know how to resolve this issue
I use Axios in my project.
When I try to get an array everything is okay but not when I request only one item - in this case, I get all values as types of these values.
This when I have one item in an array ↓
When I have more than one item in an array (that's how it should work) ↓
Has anyone encountered such a problem before?
init.js :
import axios from 'axios';
let api = null;
function getInitializedApi() {
if (api) return api;
const apiURL = getBaseUrl();
api = axios.create({
timeout: 60000,
withCredentials: true,
baseURL: apiURL,
responseType: 'json',
});
api.interceptors.response.use(undefined, (error) => {
if (error.response && error.response.status === 401) {
return Promise.reject(null);
}
return Promise.reject(error.response.data);
});
return api;
}
function getBaseUrl() {
return process.env.REACT_APP_API_URL;
}
export default getInitializedApi();
Container.js
import api from '../api';
...
const mapDispatchToProps = (dispatch) => ({
getCaselist: async (filter, sorting, page) => {
dispatch(actions.caselist.get.list.request());
try {
const { status, data, error } = await api.caselist.getList(
filter,
sorting,
page
);
console.log(data); //That's where I get the issue
// eslint-disable-next-line
if (status == 200) {
const formattedData = makeTableData('caselist', data.data);
formattedData.columns.forEach((column) => {
column.editable = false;
switch (column.accessor) {
case 'patientId':
column.show = false;
break;
case 'id':
column.show = false;
break;
default:
column.show = true;
}
});
dispatch(actions.caselist.get.list.success(formattedData));
dispatch(actions.caselist.set.page(data.page));
dispatch(actions.caselist.set.filter(data.filter));
dispatch(actions.caselist.set.sorting(data.sorting));
} else {
dispatch(actions.caselist.get.list.failure(error));
}
} catch (error) {
dispatch(actions.caselist.get.list.failure(error));
}
},
});
I'm creating a search that will print out results from the following API: https://jsonplaceholder.typicode.com/users.
At this stage I just want the data to print out as search results. Currently, the "Failed to fetch results. Please check network" error message displays after any search.
Here's my search component:
import React from "react";
import "../styles.css";
import axios from "axios";
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
query: "",
results: {},
loading: false,
message: ""
};
this.cancel = "";
}
fetchSearchResults = (updatedPageNo = "", query) => {
const pageNumber = updatedPageNo ? `&page=${updatedPageNo}` : "";
// By default the limit of results is 20
const searchUrl = `https://jsonplaceholder.typicode.com/users${query}${pageNumber}`;
if (this.cancel) {
// Cancel the previous request before making a new request
this.cancel.cancel();
}
// Create a new CancelToken
this.cancel = axios.CancelToken.source();
axios
.get(searchUrl, {
cancelToken: this.cancel.token
})
.then(res => {
const resultNotFoundMsg = !res.data.length
? "There are no more search results. Please try a new search."
: "";
this.setState({
results: res.data,
message: resultNotFoundMsg,
loading: false
});
})
.catch(error => {
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
});
};
handleOnInputChange = event => {
const query = event.target.value;
if (!query) {
this.setState({ query, results: {}, message: "" });
} else {
this.setState({ query, loading: true, message: "" }, () => {
this.fetchSearchResults(1, query);
});
}
};
renderSearchResults = () => {
const { results } = this.state;
if (Object.keys(results).length && results.length) {
return (
<ul>
{results.map(result => (
<li>{result.name}</li>
))}
</ul>
);
}
};
render() {
const { query, message } = this.state;
return (
<div className="container">
{/*Heading*/}
<h2 className="heading">Live Search: React Application</h2>
{/*Search Input*/}
<label className="search-label" htmlFor="search-input">
<input
type="text"
value={query}
id="search-input"
placeholder="Search..."
onChange={this.handleOnInputChange}
/>
<i className="fa fa-search search-icon" />
</label>
{/* Error Message*/}
{message && <p className="message">{message}</p>}
{/*Result*/}
{this.renderSearchResults()}
</div>
);
}
}
export default Search;
The reason why the code fails is in the target searchUrl.
A quick look and I can see that the searchUrl that is formed when the user types "Tim" is:
https://jsonplaceholder.typicode.com/userstim&page=1
If you look at the HTTP request there's an 404 error:
GET https://jsonplaceholder.typicode.com/userstim&page=1
[HTTP/2 404 Not Found 18ms]
So, have in mind that you should always look into the original error message, you can of course present a different message to the end user, but this would have been helpful to you:
.catch(error => {
console.log("error: ", error.message);
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
});
So, the reason why this is not working is the searchUrl pointing to an unexisting endpoint location. You can simply remove the query and see it in action!
const searchUrl = `https://jsonplaceholder.typicode.com/users`;
So, fix the searchUrl and check the API documentation to understand what to do to filter by username.
It's out of the scope for the question but you can filter the data after the request to /users if a way to fetch by name doesn't exist...
data.filter(item => item.username === query)
You need to set this.cancel to undefined when you initialise it, and after a search. Something like this:
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
query: "",
results: {},
loading: false,
message: ""
};
this.cancel = undefined;
}
fetchSearchResults = (updatedPageNo = "", query) => {
const pageNumber = updatedPageNo ? `&page=${updatedPageNo}` : "";
// By default the limit of results is 20
const searchUrl = `https://jsonplaceholder.typicode.com/users${query}${pageNumber}`;
if (this.cancel) {
// Cancel the previous request before making a new request
this.cancel.cancel();
}
// Create a new CancelToken
this.cancel = axios.CancelToken.source();
axios
.get(searchUrl, {
cancelToken: this.cancel.token
})
.then(res => {
const resultNotFoundMsg = !res.data.length
? "There are no more search results. Please try a new search."
: "";
this.setState({
results: res.data,
message: resultNotFoundMsg,
loading: false
});
this.cancel = undefined;
})
.catch(error => {
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
this.cancel = undefined;
});
};
But it's better to do it in one place:
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
query: "",
results: {},
loading: false,
message: ""
};
this.cancel = undefined;
}
fetchSearchResults = (updatedPageNo = "", query) => {
const pageNumber = updatedPageNo ? `&page=${updatedPageNo}` : "";
// By default the limit of results is 20
const searchUrl = `https://jsonplaceholder.typicode.com/users${query}${pageNumber}`;
if (this.cancel) {
// Cancel the previous request before making a new request
this.cancel.cancel();
}
// Create a new CancelToken
this.cancel = axios.CancelToken.source();
axios
.get(searchUrl, {
cancelToken: this.cancel.token
})
.then(res => {
const resultNotFoundMsg = !res.data.length
? "There are no more search results. Please try a new search."
: "";
this.setState({
results: res.data,
message: resultNotFoundMsg,
loading: false
});
})
.catch(error => {
if (axios.isCancel(error) || error) {
this.setState({
loading: false,
message: "Failed to fetch results.Please check network"
});
}
})
.finally(() => {this.cancel = undefined})
};
If your environment supports Promise.finally.
I want to logout the session of the current user after one hour of his login as well as on click of a button. I tried storing the current time stamp of the user as soon as he login using his auth-token. But got confused in achieving the proper result.
Here is the code :
Get from LocalStorage:
export default function setLocalData(key, value)
{
var responseStatus = false;
switch (key)
{
case 'currentUser':
const currentDateTime = new Date();
const updateDateTime = new Date();
const expireDateTime = new Date(updateDateTime.setHours(updateDateTime.getHours() + 2));
const currentTimestamp = Math.floor(currentDateTime.getTime() / 1000);
const expireTimeStamp = Math.floor(expireDateTime.getTime() / 1000);
const initialState = {
isLogin: true,
loginTime: currentTimestamp,
expirationTime: expireTimeStamp,
userInfo: value
};
localStorage.setItem(key, btoa(JSON.stringify(initialState)));
responseStatus = true;
break;
default:
responseStatus = false;
break;
}
return responseStatus;
}
set to LocalStorage:
export default function getLocalData(key, type='all')
{
var responseObject = null;
try
{
if(localStorage.getItem(key))
{
var response;
response = JSON.parse(atob(localStorage.getItem(key)));
switch (type)
{
case 'all':
responseObject = (response) ? response : null;
break;
default:
responseObject = null;
break;
}
}
}
catch (e)
{
responseObject = null;
}
return responseObject;
}
This is my component file where the automatic logout function needs to trigger:
class DefaultLayout extends Component {
componentDidMount(){
let token = LocalData.getLocalData('currentUser');
console.log(token);
setTimeout(()=> {
this.signOut();
}, token.expirationTime);
}
//this is triggered on clicking on logout() button
signOut(e) {
e.preventDefault();
localStorage.clear();
this.props.history.push('/login')
}
render() {
//console.log(this.props)
return ( ......
......
}
}
On console.log(token), the result achieved is:
{
expirationTime: 1575286437
isLogin: true
loginTime: 1575279237
userInfo: "eyJhbGciOiJIUz11NiIsInR5cCI6IkpXVCJ9.....
}
I am not sure if am implementing this correctly. Kindly help to figure this out.
I think the problem is here
setTimeout(()=> {
this.signOut();
}, token.expirationTime);
You are setting the timeout value to the expiration time. It should be an interval in milliseconds. So if you want the function to be triggered after 1 hr then the value should be 60 * 60 * 1000 or some calculation based on the expiration time stamp.
Look at this
class DefaultLayout extends Component {
componentDidMount(){
let token = LocalData.getLocalData('currentUser');
console.log(token);
if(token.expirationTime>==token.loginTime){
this.signOut();
}
}
//this is triggered on clicking on logout() button
signOut() {
localStorage.clear();
this.props.history.push('/login')
}
render() {
//console.log(this.props)
return ( ......
......
}
}
I am using Kurento client for video calling in a room. There is only two participants(local and remote) in a call. A client can leave the room but when a client leaves the room then the stream of that client is not shown to other client which is obvious but when again that same client wants to join in the room, the client does not gets connected because of which the other client wont see his/her stream to have video call.
Here is how i have done
import kurentoUtils from "kurento-utils";
import socketIOClient from "socket.io-client";
import {
createWebRTCPeer,
sendMessage,
createWebRTCScreenPeer
} from "./common";
const CONSTRAINTS = {
audio: true,
video: {
width: 640,
framerate: 15
}
};
class VideoRoom extends Component {
constructor(props) {
super(props);
this.state = {
startCall: false,
room: "",
clientJoined: false,
email: "",
isLoggedIn: false,
open: false,
mute: false
};
this.localStreamRef = React.createRef();
this.remoteStreamRef = React.createRef();
this.onIceCandidates = this.onIceCandidates.bind(this);
this.handleError = this.handleError.bind(this);
this.socket = null;
this.webRTC = null;
this.loginName = null;
}
handleError = (e)=> {
console.log(e);
}
onIceCandidates(candidate) {
sendMessage(
{
event: "iceCandidate",
data: candidate
},
this.socket
);
}
componentDidMount() {
this.socket = new socketIOClient("http://localhost:8443/sockets");
this.webRtcPeer = null;
this.webRtcScreenPeer = null;
const { state } = this.props.location;
if (state && state.interviewId) {
this.initiateSocket();
}
}
initiateSocket() {
const { interviewId } = this.props.location.state;
this.socket.emit("room:addUser", { interviewId, userEmail: this.state.email });
this.socket.on("ICE_CANDIDATE", async candidate => {
console.log("candidate in listener", candidate);
await this.webRTC.addIceCandidate(candidate);
});
this.socket.on("RTC:PEER", room => {
console.log("RTC:PEER", room, this.localStreamRef);
this.webRtcPeer = createWebRTCPeer(
{
localVideo: this.localStreamRef.current,
remoteVideo: this.remoteStreamRef.current,
onicecandidate: this.onIceCandidates
},
this.socket,
room
);
});
this.socket.on("client:joined", () => {
this.setState({ clientJoined: true });
});
this.socket.on("iceCandidate", (candidate) => {
console.log("GOT Candidate....");
this.webRtcPeer.addIceCandidate(candidate);
});
this.socket.on("answer", answer => {
console.log("GOT ANSWER....");
this.webRtcPeer.processAnswer(answer);
});
this.socket.on("remote:leave", () => {
console.log("LEAVE FROM REMOTE");
this.handleLeaveRoom(true);
this.setState({ clientJoined: false });
});
this.socket.on("ERROR", error => this.onError(error));
}
componentWillUnmount() {
this.socket.emit('end');
this.socket = null;
this.webRtcPeer && this.webRtcPeer.dispose();
this.webRtcPeer = null;
}
onError = error => console.error(error);
handleLeaveRoom = (remote = false) => {
if (remote) {
this.remoteStreamRef.current.srcObject = null;
} else if (
this.webRtcPeer !== null &&
this.webRtcPeer !== undefined &&
this.socket !== null
) {
this.localStreamRef.current.srcObject = null;
this.props.history.push("/interivew-id");
} else {
return ;
}
};
render() {
return (
<React.Fragment>
<Wrapper>
<Studio
{...this.state}
interviewer={this.localStreamRef}
interviewee={this.remoteStreamRef}
/>
<Controls
handleLeaveRoom={() => this.handleLeaveRoom()}
handleMute={() => this.handleMute()}
mute={this.state.mute}
handleScreenShare={this.handleScreenShare}
/>
</Wrapper>
</React.Fragment>
);
}
}
export default withRouter(VideoRoom);
server.js
socket.on("end", () => {
console.log(`closing socket in room:: ${socket.room}`);
// console.log(`socket end: room: ${room ? room : socket.room}`)
socket.disconnect(0);
});
socket.on('disconnect', () => {
console.log("Client disconnected from", socket.room);
let session = sessions[socket.room];
if(session){
session.removeClient(socket.id);
}
socket.broadcast.to(socket.room).emit('remote:leave', socket.id);
});
Here is the full code
https://gist.github.com/SanskarSans/76aee1ab4ccab7c02dc812019f1329e9
The leave room works but when trying to re join, the client does not gets connected and show his/her stream to remote client.