2023-08-31 21:18:30 +08:00

122 lines
3.6 KiB
TypeScript

import { atom } from "jotai";
import portalService, {
PortalConfig,
PortalCredential,
Prelogin,
} from "../services/portalService";
import logger from "../utils/logger";
import { redact } from "../utils/redact";
import { selectedGatewayAtom } from "./gateway";
import { loginGatewayAtom } from "./loginGateway";
import { portalAddressAtom, updatePortalDataAtom } from "./portal";
import { isProcessingAtom, statusAtom } from "./status";
/**
* The error thrown when the portal config is abnormal, can back to normal after retrying
*/
export class AbnormalPortalConfigError extends Error {
constructor(message: string) {
super(message);
this.name = "AbnormalPortalConfigError";
}
}
// Indicates whether the portal config is being fetched
// This is mainly used to show the loading indicator in the password login form
const portalConfigLoadingAtom = atom(false);
/**
* Workflow:
*
* 1. Fetch portal config
* 2. Save the portal config to the external storage
* 3. Login the gateway, which will retrieve the token and pass it
* to the background service to connect the VPN
*/
export const loginPortalAtom = atom(
(get) => get(portalConfigLoadingAtom),
async (
get,
set,
credential: PortalCredential,
prelogin: Prelogin,
configFetched?: () => void
) => {
set(statusAtom, "portal-config");
const portalAddress = get(portalAddressAtom);
if (!portalAddress) {
throw new Error("Portal is empty");
}
set(portalConfigLoadingAtom, true);
let portalConfig: PortalConfig;
try {
portalConfig = await portalService.fetchConfig(portalAddress, credential);
configFetched?.();
} catch (err) {
const isProcessing = get(isProcessingAtom);
if (!isProcessing) {
logger.info("Operation cancelled");
return;
}
throw err;
} finally {
set(portalConfigLoadingAtom, false);
}
const isProcessing = get(isProcessingAtom);
if (!isProcessing) {
logger.info("Operation cancelled");
return;
}
const { gateways, userAuthCookie, prelogonUserAuthCookie } = portalConfig;
if (!gateways.length) {
throw new AbnormalPortalConfigError("No gateway found");
}
if (userAuthCookie === "empty" || prelogonUserAuthCookie === "empty") {
throw new AbnormalPortalConfigError("Empty user auth cookie");
}
// Here, we have got the portal config successfully, refresh the cached portal data
const previousSelectedGateway = get(selectedGatewayAtom)?.name;
const selectedGateway = gateways.find(
({ name }) => name === previousSelectedGateway
);
// Update the portal data to persist it
await set(updatePortalDataAtom, {
address: portalAddress,
gateways: gateways.map(({ name, address }) => ({ name, address })),
cachedCredential: {
user: credential.user,
passwd: credential.passwd,
"portal-userauthcookie": userAuthCookie,
"portal-prelogonuserauthcookie": prelogonUserAuthCookie,
},
selectedGateway: selectedGateway?.name,
});
// Choose the best gateway
const { region } = prelogin;
const { name, address } = portalService.chooseGateway(gateways, {
region,
preferredGateway: previousSelectedGateway,
});
logger.info(`Found the preferred gateway: ${name} (${redact(address)})`);
// Log in to the gateway
await set(loginGatewayAtom, address, {
user: credential.user,
userAuthCookie,
prelogonUserAuthCookie,
});
// Update the selected gateway after a successful login
await set(selectedGatewayAtom, name);
}
);