diff --git a/public/js/calco2lato.js b/public/js/calco2lato.js new file mode 100644 index 0000000..179d7b6 --- /dev/null +++ b/public/js/calco2lato.js @@ -0,0 +1,117 @@ +// Lightweight client that talks to your PHP proxy via fetch. +// Returns Airport/Flight objects with helpful methods. + +export class Airport { + constructor(dto) { + Object.assign(this, dto); + } + get display() { + const code = this.iata || this.icao || ''; + return `${code} – ${this.name}${this.city ? ' (' + this.city + ')' : ''}`; + } +} + +export class Flight { + constructor(dto) { + Object.assign(this, dto); + } + summary() { + const s = []; + if (this.origin?.iata) s.push(this.origin.iata); + if (this.destination?.iata) s.push(this.destination.iata); + const route = s.length ? s.join(' → ') : 'Flight'; + const co2 = this.emissions?.co2_total ?? this.co2 ?? null; + return co2 != null ? `${route}: ${co2} kg CO₂e` : route; + } +} + +export class Calco2Client { + /** + * @param {string} proxyUrl e.g. "/api-proxy.php" + * @param {object} [options] + * @param {number} [options.timeoutMs=10000] + */ + constructor(proxyUrl, { timeoutMs = 10000 } = {}) { + this.proxyUrl = proxyUrl; + this.timeoutMs = timeoutMs; + } + + async _fetchJSON(payloadOrQuery) { + const { method = 'GET', query = null, body = null } = payloadOrQuery; + const url = new URL(this.proxyUrl, window.location.origin); + if (query) { + for (const [k, v] of Object.entries(query)) { + if (v !== undefined && v !== null) url.searchParams.set(k, String(v)); + } + } + + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), this.timeoutMs); + + try { + const res = await fetch(url.toString(), { + method, + headers: body ? { 'Content-Type': 'application/json' } : undefined, + body: body ? JSON.stringify(body) : undefined, + credentials: 'include', + signal: ctrl.signal, + }); + const data = await res.json().catch(() => ({})); + if (!res.ok) { + throw new Error(data?.error || `HTTP ${res.status}`); + } + return data; + } finally { + clearTimeout(t); + } + } + + // ---------- Airports ---------- + + /** + * @param {string} q + * @param {number} [limit=20] + * @param {number} [offset=0] + * @returns {Promise} + */ + async searchAirports(q, limit = 20, offset = 0) { + const data = await this._fetchJSON({ + method: 'GET', + query: { endpoint: 'airports.search', q, limit, offset } + }); + const items = Array.isArray(data?.items) ? data.items : (Array.isArray(data) ? data : []); + return items.map(a => new Airport(a)); + } + + /** @returns {Promise} */ + async getAirport(code) { + const data = await this._fetchJSON({ + method: 'GET', + query: { endpoint: 'airports.get', code } + }); + return new Airport(data); + } + + // ---------- Flights ---------- + + /** + * @param {object} params e.g. { origin: "FRA", destination: "LHR", date: "2025-09-20", pax: 1, cabin: "economy" } + * @returns {Promise} + */ + async estimateFlight(params) { + const data = await this._fetchJSON({ + method: 'POST', + body: { endpoint: 'flights.estimate', params } + }); + return new Flight(data); + } + + /** @returns {Promise} */ + async getFlight(id) { + const data = await this._fetchJSON({ + method: 'GET', + query: { endpoint: 'flights.get', id } + }); + return new Flight(data); + } +}