UNPKG

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
'use strict'; 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;