diff --git a/examples/boilerplate-react-buildless/index.html b/examples/boilerplate-react-buildless/index.html index 88bb7d83..6e6a62b4 100644 --- a/examples/boilerplate-react-buildless/index.html +++ b/examples/boilerplate-react-buildless/index.html @@ -28,7 +28,7 @@ import { createRoot } from 'https://esm.sh/react-dom@18/client?dev'; import * as zebar from 'https://esm.sh/zebar@2'; - const providers = await zebar.createProviderGroup({ + const providers = zebar.createProviderGroup({ cpu: { type: 'cpu' }, battery: { type: 'battery' }, memory: { type: 'memory' }, @@ -46,11 +46,13 @@ return (
-
CPU usage: {output.cpu.usage}
+
CPU usage: {output.cpu?.usage}
Battery charge: {output.battery?.chargePercent}
-
Memory usage: {output.memory.usage}
+
+ Memory usage: {output.memory?.usage} +
Weather temp: {output.weather?.celsiusTemp}
diff --git a/examples/boilerplate-solid-ts/src/index.tsx b/examples/boilerplate-solid-ts/src/index.tsx index 097a3786..f86d90b5 100644 --- a/examples/boilerplate-solid-ts/src/index.tsx +++ b/examples/boilerplate-solid-ts/src/index.tsx @@ -4,12 +4,11 @@ import { render } from 'solid-js/web'; import { createStore } from 'solid-js/store'; import * as zebar from 'zebar'; -const providers = await zebar.createProviderGroup({ +const providers = zebar.createProviderGroup({ cpu: { type: 'cpu' }, battery: { type: 'battery' }, memory: { type: 'memory' }, weather: { type: 'weather' }, - keyboard: { type: 'keyboard' }, }); render(() => , document.getElementById('root')!); @@ -21,12 +20,11 @@ function App() { return (
-
Keyboard: {output.keyboard.layout}
-
CPU usage: {output.cpu.usage}
+
CPU usage: {output.cpu?.usage}
Battery charge: {output.battery?.chargePercent}
-
Memory usage: {output.memory.usage}
+
Memory usage: {output.memory?.usage}
Weather temp: {output.weather?.celsiusTemp}
); diff --git a/examples/starter/vanilla.html b/examples/starter/vanilla.html index 1c731f10..6fc2cf8c 100644 --- a/examples/starter/vanilla.html +++ b/examples/starter/vanilla.html @@ -28,7 +28,7 @@ import { createRoot } from 'https://esm.sh/react-dom@18/client?dev'; import * as zebar from 'https://esm.sh/zebar@2'; - const providers = await zebar.createProviderGroup({ + const providers = zebar.createProviderGroup({ network: { type: 'network' }, cpu: { type: 'cpu' }, date: { type: 'date', formatting: 'EEE d MMM t' }, @@ -47,23 +47,23 @@ }, []); // Get icon to show for current network status. - function getNetworkIcon() { - switch (output.network.defaultInterface?.type) { + function getNetworkIcon(networkOutput) { + switch (networkOutput.defaultInterface?.type) { case 'ethernet': return ; case 'wifi': - if (output.network.defaultGateway?.signalStrength >= 80) { + if (networkOutput.defaultGateway?.signalStrength >= 80) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 65 + networkOutput.defaultGateway?.signalStrength >= 65 ) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 40 + networkOutput.defaultGateway?.signalStrength >= 40 ) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 25 + networkOutput.defaultGateway?.signalStrength >= 25 ) { return ; } else { @@ -77,21 +77,21 @@ } // Get icon to show for how much of the battery is charged. - function getBatteryIcon() { - if (output.battery.chargePercent > 90) + function getBatteryIcon(batteryOutput) { + if (batteryOutput.chargePercent > 90) return ; - if (output.battery.chargePercent > 70) + if (batteryOutput.chargePercent > 70) return ; - if (output.battery.chargePercent > 40) + if (batteryOutput.chargePercent > 40) return ; - if (output.battery.chargePercent > 20) + if (batteryOutput.chargePercent > 20) return ; return ; } // Get icon to show for current weather status. - function getWeatherIcon() { - switch (output.weather.status) { + function getWeatherIcon(weatherOutput) { + switch (weatherOutput.status) { case 'clear_day': return ; case 'clear_night': @@ -125,31 +125,35 @@
-
{output.date.formatted}
+
{output.date?.formatted}
{output.network && (
- {getNetworkIcon()} + {getNetworkIcon(output.network)} {output.network.defaultGateway?.ssid}
)} -
- - {Math.round(output.memory.usage)}% -
+ {output.memory && ( +
+ + {Math.round(output.memory.usage)}% +
+ )} -
- + {output.cpu && ( +
+ - {/* Change the text color if the CPU usage is high. */} - 85 ? 'high-usage' : ''} - > - {Math.round(output.cpu.usage)}% - -
+ {/* Change the text color if the CPU usage is high. */} + 85 ? 'high-usage' : ''} + > + {Math.round(output.cpu.usage)}% + +
+ )} {output.battery && (
@@ -157,14 +161,14 @@ {output.battery.isCharging && ( )} - {getBatteryIcon()} + {getBatteryIcon(output.battery)} {Math.round(output.battery.chargePercent)}%
)} {output.weather && (
- {getWeatherIcon()} + {getWeatherIcon(output.weather)} {Math.round(output.weather.celsiusTemp)}°C
)} diff --git a/examples/starter/with-glazewm.html b/examples/starter/with-glazewm.html index c8ac35ba..b3b5d6c5 100644 --- a/examples/starter/with-glazewm.html +++ b/examples/starter/with-glazewm.html @@ -28,7 +28,7 @@ import { createRoot } from 'https://esm.sh/react-dom@18/client?dev'; import * as zebar from 'https://esm.sh/zebar@2'; - const providers = await zebar.createProviderGroup({ + const providers = zebar.createProviderGroup({ network: { type: 'network' }, glazewm: { type: 'glazewm' }, cpu: { type: 'cpu' }, @@ -48,23 +48,23 @@ }, []); // Get icon to show for current network status. - function getNetworkIcon() { - switch (output.network.defaultInterface?.type) { + function getNetworkIcon(networkOutput) { + switch (networkOutput.defaultInterface?.type) { case 'ethernet': return ; case 'wifi': - if (output.network.defaultGateway?.signalStrength >= 80) { + if (networkOutput.defaultGateway?.signalStrength >= 80) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 65 + networkOutput.defaultGateway?.signalStrength >= 65 ) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 40 + networkOutput.defaultGateway?.signalStrength >= 40 ) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 25 + networkOutput.defaultGateway?.signalStrength >= 25 ) { return ; } else { @@ -78,21 +78,21 @@ } // Get icon to show for how much of the battery is charged. - function getBatteryIcon() { - if (output.battery.chargePercent > 90) + function getBatteryIcon(batteryOutput) { + if (batteryOutput.chargePercent > 90) return ; - if (output.battery.chargePercent > 70) + if (batteryOutput.chargePercent > 70) return ; - if (output.battery.chargePercent > 40) + if (batteryOutput.chargePercent > 40) return ; - if (output.battery.chargePercent > 20) + if (batteryOutput.chargePercent > 20) return ; return ; } // Get icon to show for current weather status. - function getWeatherIcon() { - switch (output.weather.status) { + function getWeatherIcon(weatherOutput) { + switch (weatherOutput.status) { case 'clear_day': return ; case 'clear_night': @@ -143,7 +143,7 @@ )}
-
{output.date.formatted}
+
{output.date?.formatted}
{output.glazewm && ( @@ -160,7 +160,7 @@ @@ -168,26 +168,30 @@ {output.network && (
- {getNetworkIcon()} + {getNetworkIcon(output.network)} {output.network.defaultGateway?.ssid}
)} -
- - {Math.round(output.memory.usage)}% -
+ {output.memory && ( +
+ + {Math.round(output.memory.usage)}% +
+ )} -
- + {output.cpu && ( +
+ - {/* Change the text color if the CPU usage is high. */} - 85 ? 'high-usage' : ''} - > - {Math.round(output.cpu.usage)}% - -
+ {/* Change the text color if the CPU usage is high. */} + 85 ? 'high-usage' : ''} + > + {Math.round(output.cpu.usage)}% + +
+ )} {output.battery && (
@@ -195,14 +199,14 @@ {output.battery.isCharging && ( )} - {getBatteryIcon()} + {getBatteryIcon(output.battery)} {Math.round(output.battery.chargePercent)}%
)} {output.weather && (
- {getWeatherIcon()} + {getWeatherIcon(output.weather)} {Math.round(output.weather.celsiusTemp)}°C
)} diff --git a/examples/starter/with-komorebi.html b/examples/starter/with-komorebi.html index 3aa9b8fb..8473794e 100644 --- a/examples/starter/with-komorebi.html +++ b/examples/starter/with-komorebi.html @@ -28,7 +28,7 @@ import { createRoot } from 'https://esm.sh/react-dom@18/client?dev'; import * as zebar from 'https://esm.sh/zebar@2'; - const providers = await zebar.createProviderGroup({ + const providers = zebar.createProviderGroup({ network: { type: 'network' }, komorebi: { type: 'komorebi' }, cpu: { type: 'cpu' }, @@ -48,23 +48,23 @@ }, []); // Get icon to show for current network status. - function getNetworkIcon() { - switch (output.network.defaultInterface?.type) { + function getNetworkIcon(networkOutput) { + switch (networkOutput.defaultInterface?.type) { case 'ethernet': return ; case 'wifi': - if (output.network.defaultGateway?.signalStrength >= 80) { + if (networkOutput.defaultGateway?.signalStrength >= 80) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 65 + networkOutput.defaultGateway?.signalStrength >= 65 ) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 40 + networkOutput.defaultGateway?.signalStrength >= 40 ) { return ; } else if ( - output.network.defaultGateway?.signalStrength >= 25 + networkOutput.defaultGateway?.signalStrength >= 25 ) { return ; } else { @@ -78,21 +78,21 @@ } // Get icon to show for how much of the battery is charged. - function getBatteryIcon() { - if (output.battery.chargePercent > 90) + function getBatteryIcon(batteryOutput) { + if (batteryOutput.chargePercent > 90) return ; - if (output.battery.chargePercent > 70) + if (batteryOutput.chargePercent > 70) return ; - if (output.battery.chargePercent > 40) + if (batteryOutput.chargePercent > 40) return ; - if (output.battery.chargePercent > 20) + if (batteryOutput.chargePercent > 20) return ; return ; } // Get icon to show for current weather status. - function getWeatherIcon() { - switch (output.weather.status) { + function getWeatherIcon(weatherOutput) { + switch (weatherOutput.status) { case 'clear_day': return ; case 'clear_night': @@ -139,31 +139,35 @@ )}
-
{output.date.formatted}
+
{output.date?.formatted}
{output.network && (
- {getNetworkIcon()} + {getNetworkIcon(output.network)} {output.network.defaultGateway?.ssid}
)} -
- - {Math.round(output.memory.usage)}% -
+ {output.memory && ( +
+ + {Math.round(output.memory.usage)}% +
+ )} -
- + {output.cpu && ( +
+ - {/* Change the text color if the CPU usage is high. */} - 85 ? 'high-usage' : ''} - > - {Math.round(output.cpu.usage)}% - -
+ {/* Change the text color if the CPU usage is high. */} + 85 ? 'high-usage' : ''} + > + {Math.round(output.cpu.usage)}% + +
+ )} {output.battery && (
@@ -171,14 +175,14 @@ {output.battery.isCharging && ( )} - {getBatteryIcon()} + {getBatteryIcon(output.battery)} {Math.round(output.battery.chargePercent)}%
)} {output.weather && (
- {getWeatherIcon()} + {getWeatherIcon(output.weather)} {Math.round(output.weather.celsiusTemp)}°C
)} diff --git a/packages/client-api/src/providers/battery/create-battery-provider.ts b/packages/client-api/src/providers/battery/create-battery-provider.ts index d1b43807..73a676b3 100644 --- a/packages/client-api/src/providers/battery/create-battery-provider.ts +++ b/packages/client-api/src/providers/battery/create-battery-provider.ts @@ -37,9 +37,9 @@ export interface BatteryOutput { voltage: number | null; } -export async function createBatteryProvider( +export function createBatteryProvider( config: BatteryProviderConfig, -): Promise { +): BatteryProvider { const mergedConfig = batteryProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/cpu/create-cpu-provider.ts b/packages/client-api/src/providers/cpu/create-cpu-provider.ts index 9716e2bb..d9377fde 100644 --- a/packages/client-api/src/providers/cpu/create-cpu-provider.ts +++ b/packages/client-api/src/providers/cpu/create-cpu-provider.ts @@ -30,9 +30,7 @@ export interface CpuOutput { vendor: string; } -export async function createCpuProvider( - config: CpuProviderConfig, -): Promise { +export function createCpuProvider(config: CpuProviderConfig): CpuProvider { const mergedConfig = cpuProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/create-base-provider.ts b/packages/client-api/src/providers/create-base-provider.ts index 59a2a3af..b31997cd 100644 --- a/packages/client-api/src/providers/create-base-provider.ts +++ b/packages/client-api/src/providers/create-base-provider.ts @@ -1,4 +1,3 @@ -import { Deferred } from '~/utils'; import type { ProviderConfig } from './create-provider'; export interface Provider { @@ -52,6 +51,7 @@ export interface Provider { } type UnlistenFn = () => void | Promise; +// type UnlistenFn = () => Promise; /** * Fetches next output or error from the provider. @@ -59,15 +59,15 @@ type UnlistenFn = () => void | Promise; type ProviderFetcher = (queue: { output: (nextOutput: T) => void; error: (nextError: string) => void; -}) => UnlistenFn | Promise; +}) => Promise; -export async function createBaseProvider< +export function createBaseProvider< TConfig extends ProviderConfig, TOutput, >( config: TConfig, fetcher: ProviderFetcher, -): Promise> { +): Provider { const outputListeners = new Set<(output: TOutput) => void>(); const errorListeners = new Set<(error: string) => void>(); @@ -77,28 +77,19 @@ export async function createBaseProvider< hasError: false, }; - let unlisten: UnlistenFn | null = await startFetcher(); + let unlisten: Promise | null = startFetcher(); - async function startFetcher() { - const hasFirstEmit = new Deferred(); - - const unlisten = await fetcher({ + function startFetcher() { + return fetcher({ output: output => { latestEmission = { output, error: null, hasError: false }; outputListeners.forEach(listener => listener(output)); - hasFirstEmit.resolve(); }, error: error => { latestEmission = { output: null, error, hasError: true }; errorListeners.forEach(listener => listener(error)); - hasFirstEmit.resolve(); }, }); - - // Wait for the first emission. - await hasFirstEmit.promise; - - return unlisten; } return { @@ -114,17 +105,22 @@ export async function createBaseProvider< config, restart: async () => { if (unlisten) { - await unlisten(); + await ( + await unlisten + )(); } - await startFetcher(); + unlisten = startFetcher(); }, stop: async () => { outputListeners.clear(); errorListeners.clear(); if (unlisten) { - await unlisten(); + await ( + await unlisten + )(); + unlisten = null; } }, onOutput: callback => { diff --git a/packages/client-api/src/providers/create-provider-group.ts b/packages/client-api/src/providers/create-provider-group.ts index c3eda5d3..05296c04 100644 --- a/packages/client-api/src/providers/create-provider-group.ts +++ b/packages/client-api/src/providers/create-provider-group.ts @@ -83,9 +83,9 @@ export type ProviderGroup = { * functions and variables that can change over time. Alternatively, a * single provider can be created using {@link createProvider}. */ -export async function createProviderGroup( +export function createProviderGroup( configMap: T, -): Promise> { +): ProviderGroup { const outputListeners = new Set< (outputMap: ProviderGroup['outputMap']) => void >(); @@ -94,15 +94,17 @@ export async function createProviderGroup( (errorMap: ProviderGroup['errorMap']) => void >(); - const providerMap = await createProviderMap(configMap); + const providerMap = createProviderMap(configMap); - let outputMap = {} as ProviderGroup['outputMap']; - let errorMap = {} as ProviderGroup['errorMap']; + let outputMap = Object.fromEntries( + Object.keys(providerMap).map(name => [name, null]), + ) as ProviderGroup['outputMap']; - for (const [name, provider] of Object.entries(providerMap)) { - outputMap = { ...outputMap, [name]: provider.output }; - errorMap = { ...errorMap, [name]: provider.error }; + let errorMap = Object.fromEntries( + Object.keys(providerMap).map(name => [name, null]), + ) as ProviderGroup['errorMap']; + for (const [name, provider] of Object.entries(providerMap)) { provider.onOutput(() => { outputMap = { ...outputMap, [name]: provider.output }; errorMap = { ...errorMap, [name]: null }; @@ -150,14 +152,14 @@ export async function createProviderGroup( }; } -async function createProviderMap( - configMap: T, -) { - const providerEntries = await Promise.all([ - ...Object.entries(configMap).map(async ([key, value]) => { - return [key, await createProvider(value)] as const; - }), - ]); - - return Object.fromEntries(providerEntries) as ProviderGroup['raw']; +/** + * Creates a map of names to provider instances. + */ +function createProviderMap(configMap: T) { + return Object.fromEntries( + Object.entries(configMap).map(([name, config]) => [ + name, + createProvider(config), + ]), + ) as ProviderGroup['raw']; } diff --git a/packages/client-api/src/providers/create-provider.ts b/packages/client-api/src/providers/create-provider.ts index 50592d92..b1ea4304 100644 --- a/packages/client-api/src/providers/create-provider.ts +++ b/packages/client-api/src/providers/create-provider.ts @@ -93,16 +93,15 @@ export type ProviderOutput = ProviderMap[keyof ProviderMap]['output']; * that can change over time. Alternatively, multiple providers can be * created using {@link createProviderGroup}. * - * Waits until the provider has emitted either its first output or first - * error. The provider will continue to output until its `stop` function is + * The provider will continue to output until its `stop` function is * called. * - * @throws If the provider config is invalid. *Does not throw* if the - * provider's first emission is an error. + * @throws If the provider config is invalid. Errors are emitted via the + * `onError` method. */ export function createProvider( config: T, -): Promise { +): ProviderMap[T['type']] { switch (config.type) { case 'battery': return createBatteryProvider(config) as any; diff --git a/packages/client-api/src/providers/date/create-date-provider.ts b/packages/client-api/src/providers/date/create-date-provider.ts index 063614bd..3c71f799 100644 --- a/packages/client-api/src/providers/date/create-date-provider.ts +++ b/packages/client-api/src/providers/date/create-date-provider.ts @@ -80,12 +80,12 @@ export interface DateOutput { iso: string; } -export async function createDateProvider( +export function createDateProvider( config: DateProviderConfig, -): Promise { +): DateProvider { const mergedConfig = dateProviderConfigSchema.parse(config); - return createBaseProvider(mergedConfig, queue => { + return createBaseProvider(mergedConfig, async queue => { queue.output(getDateValue()); const interval = setInterval( diff --git a/packages/client-api/src/providers/glazewm/create-glazewm-provider.ts b/packages/client-api/src/providers/glazewm/create-glazewm-provider.ts index 1cf87e9a..28495974 100644 --- a/packages/client-api/src/providers/glazewm/create-glazewm-provider.ts +++ b/packages/client-api/src/providers/glazewm/create-glazewm-provider.ts @@ -103,9 +103,9 @@ export interface GlazeWmOutput { ): Promise; } -export async function createGlazeWmProvider( +export function createGlazeWmProvider( config: GlazeWmProviderConfig, -): Promise { +): GlazeWmProvider { const mergedConfig = glazeWmProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/host/create-host-provider.ts b/packages/client-api/src/providers/host/create-host-provider.ts index 77a1a21b..82a5689b 100644 --- a/packages/client-api/src/providers/host/create-host-provider.ts +++ b/packages/client-api/src/providers/host/create-host-provider.ts @@ -31,9 +31,9 @@ export interface HostOutput { uptime: number; } -export async function createHostProvider( +export function createHostProvider( config: HostProviderConfig, -): Promise { +): HostProvider { const mergedConfig = hostProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/ip/create-ip-provider.ts b/packages/client-api/src/providers/ip/create-ip-provider.ts index 496ddce2..35cb76b4 100644 --- a/packages/client-api/src/providers/ip/create-ip-provider.ts +++ b/packages/client-api/src/providers/ip/create-ip-provider.ts @@ -30,9 +30,7 @@ export interface IpOutput { approxLongitude: number; } -export async function createIpProvider( - config: IpProviderConfig, -): Promise { +export function createIpProvider(config: IpProviderConfig): IpProvider { const mergedConfig = ipProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/keyboard/create-keyboard-provider.ts b/packages/client-api/src/providers/keyboard/create-keyboard-provider.ts index c48b24d0..8ee2b13e 100644 --- a/packages/client-api/src/providers/keyboard/create-keyboard-provider.ts +++ b/packages/client-api/src/providers/keyboard/create-keyboard-provider.ts @@ -29,9 +29,9 @@ export interface KeyboardOutput { layout: string; } -export async function createKeyboardProvider( +export function createKeyboardProvider( config: KeyboardProviderConfig, -): Promise { +): KeyboardProvider { const mergedConfig = keyboardProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/komorebi/create-komorebi-provider.ts b/packages/client-api/src/providers/komorebi/create-komorebi-provider.ts index 4fe9422c..36bd8b8c 100644 --- a/packages/client-api/src/providers/komorebi/create-komorebi-provider.ts +++ b/packages/client-api/src/providers/komorebi/create-komorebi-provider.ts @@ -121,9 +121,9 @@ export type KomorebiLayoutFlip = | 'vertical' | 'horizontal_and_vertical'; -export async function createKomorebiProvider( +export function createKomorebiProvider( config: KomorebiProviderConfig, -): Promise { +): KomorebiProvider { const mergedConfig = komorebiProviderConfigSchema.parse(config); // TODO: Update state when monitors change. diff --git a/packages/client-api/src/providers/memory/create-memory-provider.ts b/packages/client-api/src/providers/memory/create-memory-provider.ts index 102bd69c..f365e40f 100644 --- a/packages/client-api/src/providers/memory/create-memory-provider.ts +++ b/packages/client-api/src/providers/memory/create-memory-provider.ts @@ -32,9 +32,9 @@ export interface MemoryOutput { totalSwap: number; } -export async function createMemoryProvider( +export function createMemoryProvider( config: MemoryProviderConfig, -): Promise { +): MemoryProvider { const mergedConfig = memoryProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/network/create-network-provider.ts b/packages/client-api/src/providers/network/create-network-provider.ts index eba29b01..b240dcdc 100644 --- a/packages/client-api/src/providers/network/create-network-provider.ts +++ b/packages/client-api/src/providers/network/create-network-provider.ts @@ -88,9 +88,9 @@ export interface NetworkTrafficMeasure { iecUnit: string; } -export async function createNetworkProvider( +export function createNetworkProvider( config: NetworkProviderConfig, -): Promise { +): NetworkProvider { const mergedConfig = networkProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { diff --git a/packages/client-api/src/providers/weather/create-weather-provider.ts b/packages/client-api/src/providers/weather/create-weather-provider.ts index 1c406195..53a81f38 100644 --- a/packages/client-api/src/providers/weather/create-weather-provider.ts +++ b/packages/client-api/src/providers/weather/create-weather-provider.ts @@ -50,33 +50,12 @@ export interface WeatherOutput { windSpeed: number; } -export async function createWeatherProvider( +export function createWeatherProvider( config: WeatherProviderConfig, -): Promise { - let ipProvider: IpProvider | null = null; - - const mergedConfig: WeatherProviderConfig = { - ...weatherProviderConfigSchema.parse(config), - longitude: - config.longitude ?? (await getIpProvider()).output?.approxLongitude, - latitude: - config.latitude ?? (await getIpProvider()).output?.approxLatitude, - }; - - async function getIpProvider() { - return ( - ipProvider ?? (ipProvider = await createProvider({ type: 'ip' })) - ); - } +): WeatherProvider { + const mergedConfig = weatherProviderConfigSchema.parse(config); return createBaseProvider(mergedConfig, async queue => { - if (!mergedConfig.latitude || !mergedConfig.longitude) { - queue.error( - 'Failed to fetch estimate for latitude/longitude from IP address.', - ); - return () => {}; - } - return onProviderEmit(mergedConfig, ({ result }) => { if ('error' in result) { queue.error(result.error); diff --git a/packages/desktop/src/monitor_state.rs b/packages/desktop/src/monitor_state.rs index 668dd8a5..c71afcb2 100644 --- a/packages/desktop/src/monitor_state.rs +++ b/packages/desktop/src/monitor_state.rs @@ -3,7 +3,7 @@ use std::{sync::Arc, time::Duration}; use serde::Serialize; use tauri::AppHandle; use tokio::{ - sync::{broadcast, Mutex, RwLock}, + sync::{broadcast, RwLock}, task, }; use tracing::info; diff --git a/packages/desktop/src/providers/ip/ip_provider.rs b/packages/desktop/src/providers/ip/ip_provider.rs index 590769ed..37b1cdac 100644 --- a/packages/desktop/src/providers/ip/ip_provider.rs +++ b/packages/desktop/src/providers/ip/ip_provider.rs @@ -38,7 +38,7 @@ impl IpProvider { self.config.refresh_interval } - async fn run_interval(&self) -> anyhow::Result { + pub async fn run_interval(&self) -> anyhow::Result { let res = self .http_client .get("https://ipinfo.io/json") diff --git a/packages/desktop/src/providers/weather/weather_provider.rs b/packages/desktop/src/providers/weather/weather_provider.rs index 24016b5d..8b57d300 100644 --- a/packages/desktop/src/providers/weather/weather_provider.rs +++ b/packages/desktop/src/providers/weather/weather_provider.rs @@ -2,14 +2,20 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use super::open_meteo_res::OpenMeteoRes; -use crate::{impl_interval_provider, providers::ProviderOutput}; +use crate::{ + impl_interval_provider, + providers::{ + ip::{IpProvider, IpProviderConfig}, + ProviderOutput, + }, +}; #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct WeatherProviderConfig { pub refresh_interval: u64, - pub latitude: f32, - pub longitude: f32, + pub latitude: Option, + pub longitude: Option, } #[derive(Debug, Clone, PartialEq, Serialize)] @@ -57,13 +63,33 @@ impl WeatherProvider { } async fn run_interval(&self) -> anyhow::Result { + let (latitude, longitude) = { + match (self.config.latitude, self.config.longitude) { + (Some(lat), Some(lon)) => (lat, lon), + _ => { + let ip_output = IpProvider::new(IpProviderConfig { + refresh_interval: 0, + }) + .run_interval() + .await?; + + match ip_output { + ProviderOutput::Ip(ip_output) => { + (ip_output.approx_latitude, ip_output.approx_longitude) + } + _ => anyhow::bail!("Unexpected output from IP provider."), + } + } + } + }; + let res = self .http_client .get("https://api.open-meteo.com/v1/forecast") .query(&[ ("temperature_unit", "celsius"), - ("latitude", &self.config.latitude.to_string()), - ("longitude", &self.config.longitude.to_string()), + ("latitude", &latitude.to_string()), + ("longitude", &longitude.to_string()), ("current_weather", "true"), ("daily", "sunset,sunrise"), ("timezone", "auto"),