Aurora update

This commit is contained in:
2026-06-25 00:01:50 -04:00
parent b8ffc20360
commit 84707b2777
4 changed files with 107 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { AirTrafficLayer } from '@/services/airtraffic.ts'
import {api} from '@/services/api.ts';
import {AuroraLayer} from '@/services/aurora.ts';
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import Map from 'ol/Map'
import View from 'ol/View'
@@ -191,6 +192,9 @@ onMounted(async () => {
controls: [],
})
const aurora = new AuroraLayer(map)
aurora.show();
airTraffic = new AirTrafficLayer(map)
airTraffic.show()

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import {formatDate} from '@ztimson/utils';
import {ref, onMounted, computed, watchEffect} from 'vue';
import {api} from '../services/api';
import MetricRow from './MetricRow.vue';
@@ -92,7 +93,7 @@ watchEffect(() => {
<div class="divider" />
<MetricRow label="Next Full Moon" :value="new Date(d.moon_full).toLocaleDateString()" />
<MetricRow label="Next New Moon" :value="new Date(d.moon_new).toLocaleDateString()" />
<MetricRow label="Next Full Moon" :value="formatDate('MMM D', d.moon_full)" />
<MetricRow label="Next New Moon" :value="formatDate('MMM D', d.moon_new)" />
</div>
</template>

98
server/src/aurora.mjs Normal file
View File

@@ -0,0 +1,98 @@
import {adjustedInterval, Logger} from '@ztimson/utils';
const cacheOptions = {
ttl: null,
reload: null,
retry: 3,
url: null,
}
export class CacheService {
#fetchLoop;
cached = null;
lastUpdate = null;
logger;
name;
options;
pending;
constructor(name, options) {
this.name = name;
this.options = {
...cacheOptions,
ttl: options.reload,
...options
};
this.logger = new Logger(name);
if(this.options.reload) setTimeout(() => this.startLoop(), 1000);
}
async #fetchWithRetry() {
let lastError;
for(let attempt = 1; attempt <= this.options.retry; attempt++) {
try {
return await this.fetch();
} catch(err) {
lastError = err;
if(attempt < this.options.retry)
this.logger.warn(`Retrying (${attempt + 1}/${this.options.retry}): ${err.message || err.toString()}`);
}
}
throw lastError;
}
async fetch() {
if(!this.options.url) return Promise.reject('Fetch is not configured');
const resp = await fetch(this.options.url);
if(!resp.ok) throw new Error(`${resp.status} ${resp.statusText}`);
return resp.json();
}
startLoop(speed = this.options.reload) {
this.stopLoop();
this.#fetchLoop = adjustedInterval(() => this.get(), speed);
}
stopLoop() {
if(!this.#fetchLoop) return;
clearInterval(this.#fetchLoop);
this.#fetchLoop = null;
}
update(catchErr = true) {
if(this.pending) return this.pending;
this.logger.info('Fetching latest');
this.pending = this.#fetchWithRetry().then(data => {
if(data?.err) throw new Error(data.err?.stack || data.err?.message || data.err);
if(data?.error) throw new Error(data.error?.stack || data.error?.message || data.error);
this.lastUpdate = new Date();
this.cached = data || null;
this.logger.debug('Finished updating');
return {timestamp: this.lastUpdate, data: this.cached};
}).catch(err => {
if(catchErr) this.logger.error(`Failed: ${typeof err == 'object' ? (err.stackTrace || err.message) : err}`)
else throw err;
}).finally(() => this.pending = null);
return this.pending;
}
async get(wait = false) {
if(!this.cached || !this.lastUpdate || this.lastUpdate?.getTime() + this.options.ttl <= Date.now()) {
if(wait) await this.update();
else this.update();
}
return Promise.resolve({timestamp: this.lastUpdate || null, data: this.cached || null});
}
}
export class AuroraService extends CacheService {
constructor() {
super('Aurora', {
reload: 60_000 * 15,
url: 'https://services.swpc.noaa.gov/json/ovation_aurora_latest.json',
});
}
}
export const Aurora = new AuroraService();

View File

@@ -12,6 +12,7 @@ import {getAirTraffic, getAirTrafficHistory, getShapes} from './airtraffic.mjs';
import {getWeatherCondition} from './openweather.mjs';
import {lastForecast, getForecast, forecastTTL} from './forecast.mjs';
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
import {Aurora} from './aurora.mjs';
// ── Uncaught error handlers ───────────────────────────────────────────────────
@@ -145,6 +146,7 @@ app.get('/api/position', async (req, res) => {
// ── Space ─────────────────────────────────────────────────────────────────────
app.get('/api/aurora', async (req, res) => res.json(await Aurora.get()));
app.get('/api/space', async (req, res) => {
const {fields} = req.query;
res.json(filterFields(await getSpaceWeather(), fields));