UNPKG

@macalinao/grill

Version:

Modern Solana development kit for React applications with automatic account batching, caching, and transaction notifications

122 lines 4.41 kB
import { getBase64Encoder } from "@solana/kit"; import { createContext, useContext } from "react"; import { createAccountQueryKey } from "../query-keys.js"; /** * Parse a base64 encoded account notification into an EncodedAccount */ function parseAccountNotification(address, value) { // The data is [base64String, "base64"] const base64Data = value.data[0]; const data = getBase64Encoder().encode(base64Data); return { address, data, executable: value.executable, lamports: value.lamports, programAddress: value.owner, space: value.space, }; } /** * Creates a subscription manager instance. */ export function createSubscriptionManager(rpcSubscriptions, queryClient) { const subscriptions = new Map(); const setupSubscription = async (address, decoder, abortSignal) => { try { const accountNotifications = await rpcSubscriptions .accountNotifications(address, { commitment: "confirmed", encoding: "base64", }) .subscribe({ abortSignal }); for await (const notification of accountNotifications) { try { const value = notification.value; // Handle account closure (lamports = 0) if (value.lamports === 0n) { queryClient.setQueryData(createAccountQueryKey(address), null); continue; } // Parse and decode, then update cache const encodedAccount = parseAccountNotification(address, value); const decoded = decoder(encodedAccount); queryClient.setQueryData(createAccountQueryKey(address), decoded); } catch (decodeError) { console.error(`[SubscriptionManager] Error decoding account ${address}:`, decodeError); } } } catch (error) { // AbortError is expected when unsubscribing if (error instanceof Error && error.name === "AbortError") { return; } console.error(`[SubscriptionManager] Subscription error for ${address}:`, error); } }; const subscribe = (address, decoder) => { const key = address; const existing = subscriptions.get(key); if (existing) { // Increment reference count for existing subscription existing.refCount++; return () => { existing.refCount--; if (existing.refCount === 0) { existing.abortController.abort(); subscriptions.delete(key); } }; } // Create new subscription const abortController = new AbortController(); const entry = { abortController, refCount: 1, decoder: decoder, }; subscriptions.set(key, entry); // Start the WebSocket subscription void setupSubscription(address, decoder, abortController.signal); // Return unsubscribe function return () => { const currentEntry = subscriptions.get(key); if (!currentEntry) { return; } currentEntry.refCount--; if (currentEntry.refCount === 0) { currentEntry.abortController.abort(); subscriptions.delete(key); } }; }; const getSubscriptionCount = (address) => { const entry = subscriptions.get(address); return entry?.refCount ?? 0; }; return { subscribe, getSubscriptionCount, }; } /** * React context for subscription manager. */ export const SubscriptionContext = createContext(null); /** * Hook to access the subscription manager. * Must be used within a provider that sets up SubscriptionContext. * * @throws Error if used outside of a provider with SubscriptionContext */ export function useSubscriptionManager() { const context = useContext(SubscriptionContext); if (!context) { throw new Error("useSubscriptionManager must be used within a GrillProvider with subscriptions enabled"); } return context; } //# sourceMappingURL=subscription-context.js.map