Skip to content

Commit

Permalink
refactor: move to official navigraph SDK for charts and authentication (
Browse files Browse the repository at this point in the history
#8676)

* feat: initial commit

* chore: run and fix lint

* fix: various fixes

* fix: auth qr code

* feat: second and final (hopefully) pass

* fix: chart names

* fix: types

* chore: remove commented code

* refactor: checking for ultimate subscription

* chore: run lint

* fix: types

* build: bump eslint-plugin-tailwindcss

* chore: run lint

* fix: local charts and pinning

* chore: run lint

* fix: type

* fix: add navigraphAuthInfo to dependencies

Fixes simbrief auto import as auth data does not seem to be available on the first render

---------

Co-authored-by: Saschl <[email protected]>
  • Loading branch information
Benjozork and Saschl authored Jun 17, 2024
1 parent 0cc0c71 commit 958f635
Show file tree
Hide file tree
Showing 19 changed files with 416 additions and 632 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import { useInterval } from '@flybywiresim/react-components';
import React, { useEffect, useState } from 'react';
import { CloudArrowDown, ShieldLock } from 'react-bootstrap-icons';
import { toast } from 'react-toastify';
import { DeviceFlowParams } from 'navigraph/auth';
import QRCode from 'qrcode.react';
import { NavigraphClient, NavigraphSubscriptionStatus, usePersistentProperty } from '@flybywiresim/fbw-sdk';
import { NavigraphKeys, NavigraphSubscriptionStatus } from '@flybywiresim/fbw-sdk';
import { useHistory } from 'react-router-dom';
import { t } from '../../../Localization/translation';
import { useNavigraph } from '../Navigraph';
import { useNavigraphAuth } from '../../../../react/navigraph';
import { CancelToken } from '@navigraph/auth';

const NAVIGRAPH_SUBSCRIPTION_CHARTS = 'charts';
const NAVIGRAPH_SUBSCRIPTION_FMSDATA = 'fmsdata';

export type NavigraphAuthInfo =
| {
Expand All @@ -24,48 +28,45 @@ export type NavigraphAuthInfo =
};

export const useNavigraphAuthInfo = (): NavigraphAuthInfo => {
const navigraph = useNavigraph();

const [tokenAvail, setTokenAvail] = useState(false);
const [subscriptionStatus, setSubscriptionStatus] = useState<NavigraphSubscriptionStatus>(
NavigraphSubscriptionStatus.Unknown,
);
const [username] = usePersistentProperty('NAVIGRAPH_USERNAME');

useInterval(
() => {
if (tokenAvail !== navigraph.hasToken && navigraph.hasToken) {
navigraph
.fetchSubscriptionStatus()
.then(setSubscriptionStatus)
.catch(() => setSubscriptionStatus(NavigraphSubscriptionStatus.Unknown));
} else if (!navigraph.hasToken) {
setSubscriptionStatus(NavigraphSubscriptionStatus.None);
}

setTokenAvail(navigraph.hasToken);
},
1000,
{ runOnStart: true },
);

if (tokenAvail) {
return { loggedIn: tokenAvail, username, subscriptionStatus };
const navigraphAuth = useNavigraphAuth();

if (navigraphAuth.user) {
return {
loggedIn: true,
username: navigraphAuth.user.preferred_username,
subscriptionStatus: [NAVIGRAPH_SUBSCRIPTION_CHARTS, NAVIGRAPH_SUBSCRIPTION_FMSDATA].every((it) =>
navigraphAuth.user.subscriptions.includes(it),
)
? NavigraphSubscriptionStatus.Unlimited
: NavigraphSubscriptionStatus.Unknown,
};
}

return { loggedIn: false };
};

const Loading = () => {
const navigraph = useNavigraph();
const [, setRefreshToken] = usePersistentProperty('NAVIGRAPH_REFRESH_TOKEN');
interface LoadingProps {
onNewDeviceFlowParams: (params: DeviceFlowParams) => void;
}

const Loading: React.FC<LoadingProps> = ({ onNewDeviceFlowParams }) => {
const navigraph = useNavigraphAuth();
const [showResetButton, setShowResetButton] = useState(false);
const [cancelToken] = useState(CancelToken.source());

const handleResetRefreshToken = () => {
setRefreshToken('');
navigraph.authenticate();
cancelToken.cancel('reset requested by user');

navigraph.signIn((params) => {
onNewDeviceFlowParams(params);
});
};

useEffect(() => {
navigraph.signIn((params) => {
onNewDeviceFlowParams(params);
}, cancelToken.token);

const timeout = setTimeout(() => {
setShowResetButton(true);
}, 2_000);
Expand Down Expand Up @@ -94,40 +95,17 @@ const Loading = () => {
};

export const NavigraphAuthUI = () => {
const navigraph = useNavigraph();
const [params, setParams] = useState<DeviceFlowParams | null>(null);

const [displayAuthCode, setDisplayAuthCode] = useState(t('NavigationAndCharts.Navigraph.LoadingMsg').toUpperCase());

useInterval(() => {
if (navigraph.auth.code) {
setDisplayAuthCode(navigraph.auth.code);
if (params?.user_code) {
setDisplayAuthCode(params.user_code);
}
}, 1000);

const hasQr = !!navigraph.auth.qrLink;

useInterval(async () => {
if (!navigraph.hasToken) {
try {
await navigraph.getToken();
} catch (e) {
toast.error(`Navigraph Authentication Error: ${e.message}`, { autoClose: 10_000 });
}
}
}, navigraph.auth.interval * 1000);

useInterval(async () => {
try {
await navigraph.getToken();
} catch (e) {
toast.error(`Navigraph Authentication Error: ${e.message}`, { autoClose: 10_000 });
}
}, navigraph.tokenRefreshInterval * 1000);

useEffect(() => {
if (!navigraph.hasToken) {
navigraph.authenticate();
}
}, []);
const hasQr = !!params?.verification_uri_complete;

return (
<div className="bg-theme-accent flex h-full w-full items-center justify-center overflow-x-hidden rounded-lg p-6">
Expand All @@ -140,7 +118,7 @@ export const NavigraphAuthUI = () => {

<p className="mt-6 w-2/3 text-center">
{t('NavigationAndCharts.Navigraph.ScanTheQrCodeOrOpen')}{' '}
<span className="text-theme-highlight">{navigraph.auth.link}</span>{' '}
<span className="text-theme-highlight">{params?.verification_uri_complete ?? ''}</span>{' '}
{t('NavigationAndCharts.Navigraph.IntoYourBrowserAndEnterTheCodeBelow')}
</p>

Expand All @@ -154,10 +132,10 @@ export const NavigraphAuthUI = () => {
<div className="mt-16">
{hasQr ? (
<div className="rounded-md bg-white p-3">
<QRCode value={navigraph.auth.qrLink} size={400} />
<QRCode value={params.verification_uri_complete} size={400} />
</div>
) : (
<Loading />
<Loading onNewDeviceFlowParams={setParams} />
)}
</div>
</div>
Expand All @@ -175,32 +153,28 @@ export const NavigraphAuthUIWrapper: React.FC<NavigraphAuthUIWrapperProps> = ({
onSuccessfulLogin,
children,
}) => {
const [tokenAvail, setTokenAvail] = useState(false);

const navigraph = useNavigraph();
const navigraph = useNavigraphAuth();

useInterval(
() => {
if (!tokenAvail && navigraph.hasToken) {
if (navigraph.user) {
onSuccessfulLogin?.();
}

setTokenAvail(navigraph.hasToken);
},
1000,
{ runOnStart: true },
);

let ui: React.ReactNode;
if (tokenAvail) {
if (navigraph.user) {
ui = children;
} else if (showLogin) {
ui = <NavigraphAuthUI />;
} else {
ui = <NavigraphAuthRedirectUI />;
}

return NavigraphClient.hasSufficientEnv ? (
return NavigraphKeys.hasSufficientEnv ? (
<>{ui}</>
) : (
<div className="h-content-section-reduced mr-4 flex w-full items-center justify-center overflow-x-hidden">
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
setToastPresented,
setSimbriefDataPending,
} from '@flybywiresim/flypad';
import { useNavigraphAuthInfo } from '../../Apis/Navigraph/Components/Authentication';

interface InformationEntryProps {
title: string;
Expand All @@ -37,7 +38,9 @@ export const FlightWidget = () => {
const { data } = useAppSelector((state) => state.simbrief);
const simbriefDataPending = useAppSelector((state) => state.simbrief.simbriefDataPending);
const aircraftIcao = useAppSelector((state) => state.simbrief.data.aircraftIcao);
const [navigraphUsername] = usePersistentProperty('NAVIGRAPH_USERNAME');

const navigraphAuthInfo = useNavigraphAuthInfo();

const [overrideSimBriefUserID] = usePersistentProperty('CONFIG_OVERRIDE_SIMBRIEF_USERID');
const [autoSimbriefImport] = usePersistentProperty('CONFIG_AUTO_SIMBRIEF_IMPORT');
const airframeInfo = useAppSelector((state) => state.config.airframeInfo);
Expand Down Expand Up @@ -104,7 +107,10 @@ export const FlightWidget = () => {
toast.error(t('Dashboard.YourFlight.NoImportDueToBoardingOrRefuel'));
} else {
try {
const action = await fetchSimbriefDataAction(navigraphUsername ?? '', overrideSimBriefUserID ?? '');
const action = await fetchSimbriefDataAction(
(navigraphAuthInfo.loggedIn && navigraphAuthInfo.username) || '',
overrideSimBriefUserID ?? '',
);
dispatch(action);
dispatch(setFuelImported(false));
dispatch(setPayloadImported(false));
Expand All @@ -122,7 +128,7 @@ export const FlightWidget = () => {
useEffect(() => {
if (
!simbriefDataPending &&
(navigraphUsername || overrideSimBriefUserID) &&
((navigraphAuthInfo.loggedIn && navigraphAuthInfo.username) || overrideSimBriefUserID) &&
!toastPresented &&
fuelImported &&
payloadImported
Expand All @@ -141,11 +147,11 @@ export const FlightWidget = () => {
(!data || !isSimbriefDataLoaded()) &&
!simbriefDataPending &&
autoSimbriefImport === 'ENABLED' &&
(navigraphUsername || overrideSimBriefUserID)
((navigraphAuthInfo.loggedIn && navigraphAuthInfo.username) || overrideSimBriefUserID)
) {
fetchData();
}
}, []);
}, [navigraphAuthInfo]);

const simbriefDataLoaded = isSimbriefDataLoaded();

Expand Down Expand Up @@ -252,7 +258,7 @@ export const FlightWidget = () => {
<button
type="button"
onClick={fetchData}
className="text-theme-body hover:text-theme-highlight bg-theme-highlight hover:bg-theme-body border-theme-highlight flex w-full items-center justify-center space-x-4 rounded-md border-2 p-2 transition duration-100"
className="border-theme-highlight bg-theme-highlight text-theme-body hover:bg-theme-body hover:text-theme-highlight flex w-full items-center justify-center space-x-4 rounded-md border-2 p-2 transition duration-100"
>
<CloudArrowDown size={26} />
<p className="text-current">{t('Dashboard.YourFlight.ImportSimBriefData')}</p>
Expand Down
9 changes: 3 additions & 6 deletions fbw-common/src/systems/instruments/src/EFB/Efb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
FailureDefinition,
UniversalConfigProvider,
NXDataStore,
NavigraphClient,
SENTRY_CONSENT_KEY,
SentryConsentState,
useInteractionEvent,
Expand Down Expand Up @@ -38,7 +37,6 @@ import { FailuresOrchestratorProvider } from './failures-orchestrator-provider';
import { AlertModal, ModalContainer, ModalProvider, useModals } from './UtilComponents/Modals/Modals';
import { FbwLogo } from './UtilComponents/FbwLogo';
import { Tooltip } from './UtilComponents/TooltipWrapper';
import { NavigraphContext } from './Apis/Navigraph/Navigraph';
import { StatusBar } from './StatusBar/StatusBar';
import { ToolBar } from './ToolBar/ToolBar';
import { Dashboard } from './Dashboard/Dashboard';
Expand All @@ -62,6 +60,7 @@ import './Assets/Slider.scss';

import 'react-toastify/dist/ReactToastify.css';
import './toast.css';
import { NavigraphAuthProvider } from '../react/navigraph';

export interface EfbWrapperProps {
failures: FailureDefinition[]; // TODO: Move failure definition into VFS
Expand Down Expand Up @@ -181,8 +180,6 @@ export const Efb: React.FC<EfbProps> = ({ aircraftChecklistsProp }) => {
const [usingAutobrightness] = useSimVar('L:A32NX_EFB_USING_AUTOBRIGHTNESS', 'bool', 300);
const [batteryLifeEnabled] = usePersistentNumberProperty('EFB_BATTERY_LIFE_ENABLED', 1);

const [navigraph] = useState(() => new NavigraphClient());

const dispatch = useAppDispatch();

// Set the aircraft checklists received via component props in the redux store, so they can be
Expand Down Expand Up @@ -415,7 +412,7 @@ export const Efb: React.FC<EfbProps> = ({ aircraftChecklistsProp }) => {
return <EmptyBatteryScreen />;
case PowerStates.LOADED:
return (
<NavigraphContext.Provider value={navigraph}>
<NavigraphAuthProvider>
<ModalContainer />
<PowerContext.Provider value={{ powerState, setPowerState }}>
<div className="bg-theme-body" style={{ transform: `translateY(-${offsetY}px)` }}>
Expand Down Expand Up @@ -446,7 +443,7 @@ export const Efb: React.FC<EfbProps> = ({ aircraftChecklistsProp }) => {
</div>
</div>
</PowerContext.Provider>
</NavigraphContext.Provider>
</NavigraphAuthProvider>
);
default:
throw new Error('Invalid content state provided');
Expand Down
Loading

0 comments on commit 958f635

Please sign in to comment.