* CEC-371 Car ECU display (#79) * Merge Development (#53) * Use responsive iframe control for charts (#49) * Use responsive iframe control to charts * Move external Grafana link to Dashboard page * Remove unused embedded style class * Add button label * added delete button to deploy packages * Fix unit test warning Remove unused route from test * Fix styling of button * minor fixes per pr review Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> * Development Merge (#57) * CEC-287 Car connection status (#59) (#60) * Car connection status * Formatting * Merge Development (#64) * Add connection status to vehicles page * ConnectedIcon control * Handle Style * Development (#67) * preliminary map for vehicles * weird zoom bug * passing react tests * fixing warnings and updating snapshots * update node environment to 14 * addressing comments by changing variable types and adding styles to home page title * adding CODEOWNERS file * fixing token error * CEC-371 Update car ECUs display (#78) * Clean up className styles Update car status page to show update and ECUs * Add update ecu version button Show all ECUs on car status page Only show car ecus for search Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> * CEC-394 Car update log (#81) * CEC-394 Car update status control * Remove Datadog RUM Remove package update components Move control components into Controls folder Add Car update status page * Display update status log Clean up unused update package code * Remove console.logs * no vars * adding timestamp to vehicle popup * modifying vehicle data query * removing extraneous code * removing console log * Clean up SonarCloud warnings (#83) * Clean up SonarCloud warnings * Bogus security warning * Fix another warning * Fix unauthorized locations request * Fix update progress control * CEC-563 New manifest format (#88) * Add ManifestCreateContext Update create manifest page * Finish UI changes and API integration * Fixes * Fix test * Remove manifest ECU file version and type * Fixes * Add manifest ecu file type control * Fix Sonar warnings * Fix test * Update codeowners * Formatting * CEC-553 Change file type to string (#90) * CEC-553 File type uses string enum * Fix test timeout * Fix * Merge development * Increase timeout * Clean up (#95) * Clean up Mock missing methods * Smell Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <dtaylor@fiskerinc.com>
171 lines
4.1 KiB
JavaScript
171 lines
4.1 KiB
JavaScript
import React, { useContext, useEffect, useState } from "react";
|
|
import auth from "../../services/auth";
|
|
import getTimerWorker from "../../services/getTimerWorker";
|
|
import { parsePayload } from "../../utils/jwt";
|
|
import { getGroups } from "../../utils/roles";
|
|
|
|
const UserContext = React.createContext();
|
|
|
|
export const UserProvider = ({ children }) => {
|
|
const [fetching, setFetching] = useState(false);
|
|
const [token, setToken] = useState(null);
|
|
const [groups, setGroups] = useState(null);
|
|
const [error, setError] = useState(null);
|
|
let timer;
|
|
|
|
useEffect(() => {
|
|
try {
|
|
if (!localStorage) return;
|
|
const t = JSON.parse(localStorage.getItem("token"));
|
|
if (!t) return;
|
|
if (!t.idToken || !t.idToken.jwtToken) throw new Error("Invalid token");
|
|
setToken(t);
|
|
} catch (e) {
|
|
document.location = signOut();
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!token) return;
|
|
verifyToken();
|
|
return () => {
|
|
if (timer) timer.terminate();
|
|
};
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [token]);
|
|
|
|
const refreshTokens = async () => {
|
|
if (!token || !token.refreshToken || !token.refreshToken.token) return null;
|
|
return refresh(token.refreshToken.token);
|
|
};
|
|
|
|
const startSessionTimer = () => {
|
|
if (!token || !token.idToken || !token.idToken.jwtToken) {
|
|
throw new Error("No id token");
|
|
}
|
|
const payload = parsePayload(token.idToken.jwtToken);
|
|
if (!payload || !payload.exp) throw new Error("Bad id token payload");
|
|
const duration = 1000 * payload.exp - new Date().getTime();
|
|
if (!timer) {
|
|
timer = getTimerWorker();
|
|
timer.onMessage(async (e) => {
|
|
if (e.data === "timeout") {
|
|
const t = await refreshTokens();
|
|
if (t && !t.error) return;
|
|
document.location = signOut();
|
|
}
|
|
});
|
|
}
|
|
timer.start(duration);
|
|
};
|
|
|
|
const verifyToken = async () => {
|
|
try {
|
|
const {
|
|
idToken: { jwtToken: idToken },
|
|
} = token;
|
|
const result = await auth.verify(idToken);
|
|
|
|
if (!result || !result.valid) {
|
|
const t = await refreshTokens();
|
|
if (!t || t.error) throw new Error("Unable to refresh token");
|
|
}
|
|
|
|
setGroups(getGroups(idToken));
|
|
startSessionTimer();
|
|
} catch (e) {
|
|
setError(`Verify error. ${e.message}`);
|
|
document.location = signOut();
|
|
}
|
|
};
|
|
|
|
const signIn = async (code) => {
|
|
let result = null;
|
|
|
|
try {
|
|
if (!code) return;
|
|
|
|
setFetching(true);
|
|
setError(null);
|
|
|
|
result = await auth.signIn(code);
|
|
if (result.message) {
|
|
throw new Error(result.message);
|
|
}
|
|
|
|
signedIn(result);
|
|
} catch (err) {
|
|
setError(`Sign in error. ${err.message}`);
|
|
} finally {
|
|
setFetching(false);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
const signOut = () => {
|
|
setGroups(null);
|
|
setToken(null);
|
|
if (localStorage) {
|
|
localStorage.removeItem("token");
|
|
}
|
|
return getLogoutURL();
|
|
};
|
|
|
|
const signedIn = (value) => {
|
|
setToken(value);
|
|
if (!localStorage || !value || !value.idToken) return;
|
|
localStorage.setItem("token", JSON.stringify(value));
|
|
};
|
|
|
|
const refresh = async (value) => {
|
|
let result = null;
|
|
|
|
try {
|
|
if (!value) {
|
|
throw new Error("Token required");
|
|
}
|
|
setFetching(true);
|
|
setError(null);
|
|
|
|
result = await auth.refresh(value);
|
|
|
|
if (result.message) {
|
|
throw new Error(result.message);
|
|
}
|
|
signedIn(result);
|
|
} catch (err) {
|
|
setError(`Refresh error. ${err.message}`);
|
|
} finally {
|
|
setFetching(false);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
const getAuthorizeURL = () => auth.ssoAuthorize();
|
|
const getLogoutURL = () => auth.ssoLogout();
|
|
|
|
return (
|
|
<UserContext.Provider
|
|
value={{
|
|
error,
|
|
fetching,
|
|
groups,
|
|
token,
|
|
getAuthorizeURL,
|
|
getLogoutURL,
|
|
setError,
|
|
signIn,
|
|
signOut,
|
|
refresh,
|
|
}}
|
|
>
|
|
{children}
|
|
</UserContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useUserContext = () => useContext(UserContext);
|