atriusmaps-node-sdk
Version:
This project provides an API to Atrius Personal Wayfinder maps within a Node environment. See the README.md for more information
159 lines (153 loc) • 5.96 kB
JavaScript
;
var FlexSearch = require('flexsearch');
var luxon = require('luxon');
var R = require('ramda');
var flightDetailsMapper = require('./flightDetailsMapper.js');
var utils = require('./utils.js');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var R__namespace = /*#__PURE__*/_interopNamespaceDefault(R);
function create(app, config) {
const createIndex = () => new FlexSearch.Index({
tokenize: "forward"
});
const state = {
lastUpdated: 0,
flights: { dep: {}, arr: {} },
index: { dep: createIndex(), arr: createIndex() },
gates: null,
apiError: false
};
const init = async () => {
state.tz = await app.bus.get("venueData/getVenueTimezone");
};
const enrichData = (flights, airportsArg, airlinesArg, flightType) => {
const airports = airportsArg.reduce(
(acc, airport) => ({ ...acc, [airport.iata]: airport }),
{}
);
const airlines = airlinesArg.flatMap((airline) => [
{ code: airline.iata, airline },
{ code: airline.icao, airline }
]).filter((entry) => Boolean(entry.code)).reduce((acc, { code, airline }) => ({ ...acc, [code]: airline }), {});
return flights.map((flight) => {
const gateId = flightType === flightDetailsMapper.FlightType.DEPARTURE ? R__namespace.pathOr("", ["airportResources", "departureGate"], flight) : R__namespace.pathOr("", ["airportResources", "arrivalGate"], flight);
const gatePoi = state.gates?.[String(gateId)];
return {
...flight,
flightType,
gatePoi,
airline: airlines[flight.carrierFsCode],
departureAirport: airports[flight.departureAirportFsCode],
arrivalAirport: airports[flight.arrivalAirportFsCode]
};
});
};
const updateIndexes = (type, flightStatuses) => {
state.index[type] = createIndex();
state.flights[type] = {};
flightStatuses.forEach((status) => {
state.index[type].add(status.flightId, createFlightSearchEntry(status));
state.flights[type][status.flightId] = status;
});
};
const searchPropertiesPaths = [
["flightNumber"],
["airline", "iata"],
["airline", "name"],
["arrivalAirport", "iata"],
["arrivalAirport", "city"],
["departureAirport", "iata"],
["departureAirport", "city"]
];
const createFlightSearchEntry = (flight) => searchPropertiesPaths.map((searchPath) => R__namespace.path(searchPath, flight)).filter((value) => typeof value === "string" && value.length > 0).join(" ");
const indexGatePois = async () => {
if (state.gates === null) {
const gatePois = await app.bus.get("poi/getByCategoryId", {
categoryId: "gate"
});
const parseGate = ({ name }) => R__namespace.tail(name.replace(",", "").split(" "));
const pairs = Object.values(gatePois).flatMap(
(poi) => parseGate(poi).map((gateId) => [gateId, poi])
);
state.gates = R__namespace.fromPairs(pairs);
}
return state;
};
const updateFlights = async () => {
await indexGatePois();
const venueData = await app.bus.get("venueData/getVenueData");
const params = new URLSearchParams();
const locale = app.i18n?.().language;
if (locale) {
params.append("locale", locale);
}
if (config?.timeRange) {
const now = Date.now();
params.append("startDate", new Date(now - config.timeRange.beforeNowMs).toISOString());
params.append("endDate", new Date(now + config.timeRange.afterNowMs).toISOString());
}
const baseURL = `https://marketplace.locuslabs.com/venueId/${venueData.baseVenueId}/flight-status`;
const apiURL = params.toString() ? `${baseURL}?${params.toString()}` : baseURL;
try {
const response = await fetch(apiURL);
const data = await response.json();
[flightDetailsMapper.FlightType.ARRIVAL, flightDetailsMapper.FlightType.DEPARTURE].forEach((type) => {
const key = type === flightDetailsMapper.FlightType.DEPARTURE ? "departures" : "arrivals";
const { airports, airlines } = data.data[key].appendix;
updateIndexes(type, enrichData(data.data[key].flightStatuses, airports, airlines, type));
});
state.apiError = false;
} catch (error) {
state.apiError = true;
console.error("Error from flight status api call", error);
[flightDetailsMapper.FlightType.ARRIVAL, flightDetailsMapper.FlightType.DEPARTURE].forEach((type) => updateIndexes(type, []));
} finally {
state.lastUpdated = Date.now();
}
return state;
};
const checkFlights = async () => {
if (Date.now() - state.lastUpdated > 60 * 1e3) {
await updateFlights();
}
};
app.bus.on("flightStatus/query", async ({ term = null, type = flightDetailsMapper.FlightType.DEPARTURE } = {}) => {
await checkFlights();
return utils.searchFlights(term, type, state);
});
app.bus.on("flightStatus/getFlight", async ({ flightId }) => {
await checkFlights();
return state.flights.dep[flightId] || state.flights.arr[flightId];
});
app.bus.on(
"flightStatus/mapFlightDetails",
async ({ flight }) => flightDetailsMapper.mapFlightDetails(luxon.DateTime.local(), flight, app.gt(), state.tz, app.i18n?.().language ?? "en-US")
);
app.bus.on(
"flightStatus/mapFlightListItems",
async ({ flights }) => flights.map((flight) => flightDetailsMapper.mapFlightListItem(luxon.DateTime.local(), flight, app.gt(), state.tz, app.i18n?.().language ?? "en-US")).sort(flightDetailsMapper.flightComparator)
);
return {
init,
internal: {
updateFlights,
indexGatePois
}
};
}
exports.create = create;