Files
Shade/packages/shade-widgets/src/useShadeState.ts

76 lines
2.1 KiB
TypeScript
Raw Normal View History

2026-04-10 19:00:21 +02:00
import { useEffect, useState, useCallback } from 'react';
import { useShadeContext } from './ShadeProvider.js';
export interface ShadeState {
identity: {
fingerprint: string | null;
registrationId: number | null;
lastInitialized: number | null;
lastRotated: number | null;
};
sessions: Array<{
address: string;
remoteIdentityKeyHash: string;
messageCountSent: number;
messageCountReceived: number;
lastActivity: number;
dhRatchetSteps: number;
}>;
prekeys: {
oneTimeRemaining: number;
lastGenerated: number | null;
lastConsumed: number | null;
signedPreKeyId: number | null;
signedPreKeyLastRotated: number | null;
};
retiredIdentities: number;
server: {
registeredIdentities: string[];
totalBundleFetches: number;
totalReplenishes: number;
totalDeleted: number;
totalRateLimited: number;
};
}
export interface UseShadeStateResult {
state: ShadeState | null;
loading: boolean;
error: Error | null;
refresh: () => Promise<void>;
}
/**
* Hook that polls the observer's /api/state endpoint at the configured interval.
*/
export function useShadeState(): UseShadeStateResult {
const ctx = useShadeContext();
const [state, setState] = useState<ShadeState | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchState = useCallback(async () => {
try {
const res = await fetch(`${ctx.observerUrl}/api/state`, {
headers: { Authorization: `Bearer ${ctx.token}` },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = (await res.json()) as ShadeState;
setState(json);
setError(null);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}, [ctx.observerUrl, ctx.token]);
useEffect(() => {
fetchState();
const interval = setInterval(fetchState, ctx.pollIntervalMs);
return () => clearInterval(interval);
}, [fetchState, ctx.pollIntervalMs]);
return { state, loading, error, refresh: fetchState };
}