Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions src/app/api/purchase/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { nanoid } from "nanoid";
import { FlightLib__factory, FlightOracle__factory, FlightProduct__factory, FlightUSD__factory } from "../../../contracts/flight";
import { IPolicyService__factory } from "../../../contracts/gif";
import { IBundleService__factory, IPoolService__factory } from "../../../contracts/gif/factories/pool";
import { AirportBlacklistedError, AirportNotWhitelistedError, TransactionFailedException } from "../../../types/errors";
import { AirportBlacklistedError, AirportNotWhitelistedError, FlightNotFoundError, InconsistentFlightDataError, TransactionFailedException } from "../../../types/errors";
import { Airport } from "../../../types/flightstats/airport";
import { ApplicationData, PermitData, PurchaseRequest } from "../../../types/purchase_request";
import { LOGGER } from "../../../utils/logger_backend";
Expand Down Expand Up @@ -60,7 +60,17 @@ export async function POST(request: Request) {
return Response.json({
error: "BALANCE_ERROR"
}, { status: 500 });
} else {
} else if (err instanceof FlightNotFoundError) {
return Response.json({
error: "NO_FLIGHT_FOUND",
message: err.message,
}, { status: 400 });
} else if (err instanceof InconsistentFlightDataError) {
return Response.json({
error: "INCONSISTENT_DATA",
message: err.message,
}, { status: 400 });
} else {
// @ts-expect-error unknown error
LOGGER.error(`unexpected error: ${err.message}`);
return Response.json({
Expand Down Expand Up @@ -94,19 +104,19 @@ async function validateFlightPlan(reqId: string, application: ApplicationData) {
const response = await fetch(url);

if (!response.ok) {
throw new Error(`[${reqId}] Flight not found on flightstats api`);
throw new FlightNotFoundError(`[${reqId}] Flight not found on flightstats api`);
}

const flightData = await response.json();

const scheduledFlights = flightData.scheduledFlights;
if (scheduledFlights.length === 0) {
throw new Error(`[${reqId}] Flight not found (1)`);
throw new FlightNotFoundError(`[${reqId}] Flight not found (1)`);
}

const appendix = flightData.appendix;
if (appendix.length === 0) {
throw new Error(`[${reqId}] Flight not found (2)`);
throw new FlightNotFoundError(`[${reqId}] Flight not found (2)`);
}
const airports = appendix.airports.map((airport: Airport) => airport.iata) as string[];
LOGGER.debug(`[${reqId}] airports in flightPlan: ${JSON.stringify(airports)}`);
Expand All @@ -129,19 +139,22 @@ async function validateFlightPlan(reqId: string, application: ApplicationData) {

const departureAirportIataFlightStats = appendix.airports.find((airport: Airport) => airport.fs === scheduledFlights[0].departureAirportFsCode)?.iata;
if (departureAirportIataFlightStats !== application.departureAirport) {
throw new Error(`[${reqId}] Departure airport invalid`);
throw new InconsistentFlightDataError(`[${reqId}] Departure airport invalid`);
}

const arrivalAirportIataFlightStats = appendix.airports.find((airport: Airport) => airport.fs === scheduledFlights[0].arrivalAirportFsCode)?.iata;
if (arrivalAirportIataFlightStats !== application.arrivalAirport) {
throw new Error(`[${reqId}] Arrival airport invalid`);
throw new InconsistentFlightDataError(`[${reqId}] Arrival airport invalid`);
}

LOGGER.debug(`[${reqId}] airports whitelisted`);
} catch (err) {
if (err instanceof AirportBlacklistedError || err instanceof AirportNotWhitelistedError || err instanceof FlightNotFoundError || err instanceof InconsistentFlightDataError) {
throw err;
}
// @ts-expect-error error has field message
LOGGER.error(err.message);
throw new Error(`[${reqId}] Flight not found`);
throw new FlightNotFoundError(`[${reqId}] Flight not found`);
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/hooks/api/use_local_api.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApplicationData, PermitData } from "../../types/purchase_request";
import { PurchaseFailedError, PurchaseNotPossibleError } from "../../utils/error";
import { Reason } from "../../types/errors";

// @ts-expect-error BigInt is not defined in the global scope
BigInt.prototype.toJSON = function () {
Expand Down Expand Up @@ -29,6 +30,16 @@ export function useLocalApi() {
throw new PurchaseFailedError(result.transaction, result.decodedError);
} else if (result.error === "BALANCE_ERROR") {
throw new PurchaseNotPossibleError();
} else if (result.error === "NO_FLIGHT_FOUND") {
const err = new Error(result.message);
// @ts-expect-error adding custom field
err.reason = Reason.NO_FLIGHT_FOUND;
throw err;
} else if (result.error === "INCONSISTENT_DATA") {
const err = new Error(result.message);
// @ts-expect-error adding custom field
err.reason = Reason.INCONSISTENT_DATA;
throw err;
} else {
throw new Error(`Error sending purchase protection request: ${result.statusText}`);
}
Expand Down
14 changes: 9 additions & 5 deletions src/hooks/use_application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { useERC20Contract } from "./onchain/use_erc20_contract";
import { useFlightDelayProductContract } from "./onchain/use_flightdelay_product";
import { useWallet } from "./onchain/use_wallet";
import { setGeneralErrorMessage } from "../redux/slices/common";
import { EVENT_API_ERROR, EVENT_BLACKLISTED_ARRIVAL_AIRPORT, EVENT_BLACKLISTED_DEPARTURE_AIRPORT, EVENT_INSUFFICIENT_BALANCE, EVENT_INVALID_CHAIN, EVENT_NON_WHITELISTED_AIRPORT, EVENT_PERMIT_SIGNED, EVENT_PURACHASE_SUCCESSFUL, EVENT_PURCHASE_FAILED_UNKNOWN_ERROR, EVENT_PURCHASE_NOT_POSSIBLE, EVENT_PURCHASE_STARTED, EVENT_RISKPOOL_FULL, EVENT_USER_REJECTED, useAnalytics } from "./use_analytics";
import { EVENT_API_ERROR, EVENT_NO_FLIGHT_FOUND, EVENT_BLACKLISTED_ARRIVAL_AIRPORT, EVENT_BLACKLISTED_DEPARTURE_AIRPORT, EVENT_INSUFFICIENT_BALANCE, EVENT_INVALID_CHAIN, EVENT_NON_WHITELISTED_AIRPORT, EVENT_PERMIT_SIGNED, EVENT_PURACHASE_SUCCESSFUL, EVENT_PURCHASE_FAILED_UNKNOWN_ERROR, EVENT_PURCHASE_NOT_POSSIBLE, EVENT_PURCHASE_STARTED, EVENT_RISKPOOL_FULL, EVENT_USER_REJECTED, useAnalytics } from "./use_analytics";
import { Reason } from "../types/errors";

export default function useApplication() {
const { t } = useTranslation();
Expand Down Expand Up @@ -161,16 +162,19 @@ export default function useApplication() {
console.log("purchase result", result);

dispatch(setPolicy({policyNftId: result.policyNftId, riskId: result.riskId}));
} catch (err) {
} catch (err: unknown) {
if (err instanceof PurchaseFailedError) {
console.log("purchase failed", err);
dispatch(setError({ message: `${t("error.purchase_failed")} (${err.decodedError?.reason || "unknown error"})`, level: "error" }));
} else if (err instanceof PurchaseNotPossibleError) {
dispatch(setError({ message: t("error.purchase_currently_not_possible"), level: "error" }));
} else if (err instanceof Error && 'reason' in err && err.reason === Reason.NO_FLIGHT_FOUND) {
dispatch(setError({ message: t("error.no_flight_found"), level: "error", reason: Reason.NO_FLIGHT_FOUND }));
trackEvent(EVENT_NO_FLIGHT_FOUND, { category: "purchase"});
} else if (err instanceof Error && 'reason' in err && err.reason === Reason.INCONSISTENT_DATA) {
dispatch(setError({ message: t("error.inconsistent_data"), level: "error", reason: Reason.INCONSISTENT_DATA }));
} else {
// @ts-expect-error code is custom field for metamask error
if (err.code !== undefined) {
// @ts-expect-error code is custom field for metamask error
if (err && typeof err === 'object' && 'code' in err) {
if (err.code === "ACTION_REJECTED") {
dispatch(setError({ message: t("error.user_rejected"), level: "error" }));
trackEvent(EVENT_USER_REJECTED, { category: "purchase"});
Expand Down
5 changes: 4 additions & 1 deletion src/redux/slices/flightData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,12 @@ export const flightDataSlice = createSlice({
state.flightNumber = action.payload.flightNumber;
state.departureDate = action.payload.departureDate;
},
setError(state, action: PayloadAction<{message: string, level: string}>) {
setError(state, action: PayloadAction<{message: string, level: string, reason?: Reason}>) {
state.errorMessage = action.payload.message;
state.errorLevel = action.payload.level;
if (action.payload.reason !== undefined) {
state.errorReasonApi = action.payload.reason;
}
},
resetFlightData(state) {
// assign initial state
Expand Down
12 changes: 12 additions & 0 deletions src/types/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,15 @@ export class AirportNotWhitelistedError extends Error {
super(`Airport ${airport} is not whitelisted`);
}
}

export class FlightNotFoundError extends Error {
constructor(msg?: string) {
super(msg || "Flight not found");
}
}

export class InconsistentFlightDataError extends Error {
constructor(msg?: string) {
super(msg || "Inconsistent flight data");
}
}