Files
kapteins-daagbok/client/src/services/weather.ts
T

94 lines
2.6 KiB
TypeScript

import { apiFetch } from './api.js'
import { getOwmApiKeyForActiveUser } from './userPreferences.js'
import {
type OwmAnalyticsSource,
PlausibleEvents,
trackPlausibleEvent
} from './analytics.js'
export class WeatherApiError extends Error {
code: 'NO_KEY' | 'OFFLINE' | 'REQUEST_FAILED' | 'UNAUTHORIZED' | 'NOT_FOUND' | 'BAD_REQUEST'
constructor(
message: string,
code: 'NO_KEY' | 'OFFLINE' | 'REQUEST_FAILED' | 'UNAUTHORIZED' | 'NOT_FOUND' | 'BAD_REQUEST' = 'REQUEST_FAILED'
) {
super(message)
this.name = 'WeatherApiError'
this.code = code
}
}
const OWM_FETCH_TIMEOUT_MS = 20_000
export async function fetchOpenWeatherCurrent(
params: {
lat?: string
lon?: string
q?: string
},
options?: { analyticsSource: OwmAnalyticsSource }
): Promise<Record<string, unknown>> {
if (!navigator.onLine) {
throw new WeatherApiError('Offline', 'OFFLINE')
}
const searchParams = new URLSearchParams()
if (params.lat && params.lon) {
searchParams.set('lat', params.lat)
searchParams.set('lon', params.lon)
} else if (params.q?.trim()) {
searchParams.set('q', params.q.trim())
} else {
throw new WeatherApiError('lat/lon or location query required', 'BAD_REQUEST')
}
const userKey = getOwmApiKeyForActiveUser().trim()
const headers: Record<string, string> = {}
if (userKey) headers['X-OWM-Api-Key'] = userKey
const controller = new AbortController()
const timeoutId = window.setTimeout(() => controller.abort(), OWM_FETCH_TIMEOUT_MS)
let res: Response
try {
res = await apiFetch(`/api/weather/current?${searchParams.toString()}`, {
headers,
signal: controller.signal
})
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
throw new WeatherApiError('Weather request timed out')
}
throw err
} finally {
window.clearTimeout(timeoutId)
}
if (res.status === 503) {
throw new WeatherApiError('No OpenWeatherMap API key configured', 'NO_KEY')
}
if (res.status === 401) {
throw new WeatherApiError('Invalid OpenWeatherMap API key', 'UNAUTHORIZED')
}
if (res.status === 404) {
throw new WeatherApiError('Location or coordinates not found', 'NOT_FOUND')
}
if (res.status === 400) {
throw new WeatherApiError('Invalid or missing location parameters', 'BAD_REQUEST')
}
const data = await res.json()
if (!res.ok) {
throw new WeatherApiError('Weather API rejected the request')
}
if (options?.analyticsSource) {
trackPlausibleEvent(PlausibleEvents.OWM_WEATHER_FETCHED, {
source: options.analyticsSource
})
}
return data
}