import { ref, onMounted } from 'vue'; import { DEFAULT_CHAIN_ID } from '@/config'; import { resolveGraphqlEndpoint } from '@/utils/graphqlRetry'; import { KRAIKEN_LIB_VERSION, STACK_META_ID } from 'kraiken-lib/version'; const WEB_APP_VERSION = __APP_VERSION__; type GraphqlError = { message?: string | null }; type GraphqlResponse = { data?: T; errors?: GraphqlError[]; }; interface StackMetaRecord { contractVersion?: number | null; ponderVersion?: string | null; kraikenLibVersion?: number | null; updatedAt?: string | null; } interface StackMetaResult { stackMeta?: StackMetaRecord | null; } export interface StackVersions { contractVersion: number | null; ponderVersion: string | null; indexerLibVersion: number | null; kraikenLibVersion: number; webAppVersion: string; updatedAt: bigint | null; } export interface VersionStatus { isValid: boolean; error?: string; contractVersion?: number; indexerLibVersion?: number; ponderVersion?: string | null; libVersion: number; webAppVersion: string; } const STACK_META_QUERY = ` query StackMeta($id: String!) { stackMeta(id: $id) { contractVersion ponderVersion kraikenLibVersion updatedAt } } `; const versionStatus = ref({ isValid: true, libVersion: KRAIKEN_LIB_VERSION, webAppVersion: WEB_APP_VERSION, }); const stackVersions = ref({ contractVersion: null, ponderVersion: null, indexerLibVersion: null, kraikenLibVersion: KRAIKEN_LIB_VERSION, webAppVersion: WEB_APP_VERSION, updatedAt: null, }); const isChecking = ref(false); const hasChecked = ref(false); function formatError(error: unknown): string { if (error instanceof Error && error.message) { return error.message; } if (typeof error === 'string' && error.length > 0) { return error; } return 'Failed to load stack version metadata'; } function sanitizeNumber(value: unknown): number | null { const parsed = typeof value === 'number' ? value : Number(value ?? Number.NaN); return Number.isFinite(parsed) ? parsed : null; } export function useVersionCheck() { async function checkVersions(graphqlEndpointOverride?: string) { if (isChecking.value) return versionStatus.value; isChecking.value = true; try { const endpoint = resolveGraphqlEndpoint(DEFAULT_CHAIN_ID, graphqlEndpointOverride); const response = await fetch(endpoint, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ query: STACK_META_QUERY, variables: { id: STACK_META_ID }, }), }); if (!response.ok) { throw new Error(`GraphQL stackMeta request failed (HTTP ${response.status})`); } const payload = (await response.json()) as GraphqlResponse; const apiErrors = payload.errors?.filter(err => err?.message); if (apiErrors && apiErrors.length > 0) { throw new Error(apiErrors.map(err => err.message).join(', ')); } const meta = payload.data?.stackMeta; if (!meta) { throw new Error('Stack metadata unavailable (stackMeta query returned null)'); } const contractVersion = sanitizeNumber(meta.contractVersion); const ponderVersion = meta.ponderVersion ?? null; const indexerLibVersion = sanitizeNumber(meta.kraikenLibVersion); const updatedAt = meta.updatedAt ? BigInt(meta.updatedAt) : null; const libMismatch = indexerLibVersion !== null && indexerLibVersion !== KRAIKEN_LIB_VERSION; stackVersions.value = { contractVersion, ponderVersion, indexerLibVersion, kraikenLibVersion: KRAIKEN_LIB_VERSION, webAppVersion: WEB_APP_VERSION, updatedAt, }; versionStatus.value = { isValid: !libMismatch, error: libMismatch ? `Stack mismatch: Ponder reports kraiken-lib v${indexerLibVersion} (frontend expects v${KRAIKEN_LIB_VERSION}). Restart the stack to rebuild containers.` : undefined, contractVersion: contractVersion ?? undefined, indexerLibVersion: indexerLibVersion ?? undefined, ponderVersion, libVersion: KRAIKEN_LIB_VERSION, webAppVersion: WEB_APP_VERSION, }; } catch (error) { versionStatus.value = { isValid: false, error: formatError(error), contractVersion: stackVersions.value.contractVersion ?? undefined, indexerLibVersion: stackVersions.value.indexerLibVersion ?? undefined, ponderVersion: stackVersions.value.ponderVersion, libVersion: KRAIKEN_LIB_VERSION, webAppVersion: WEB_APP_VERSION, }; } finally { hasChecked.value = true; isChecking.value = false; } return versionStatus.value; } return { versionStatus, stackVersions, checkVersions, isChecking, hasChecked, }; } export function useVersionCheckOnMount(graphqlUrl?: string) { const { versionStatus, stackVersions, checkVersions, isChecking, hasChecked } = useVersionCheck(); onMounted(async () => { await checkVersions(graphqlUrl); }); return { versionStatus, stackVersions, isChecking, hasChecked, checkVersions, }; }