UNPKG

@four-leaf-studios/rl-socket-hook

Version:

A tiny React wrapper around a Rocket League WebSocket plugin (`ws://localhost:49122`). It provides:

88 lines (85 loc) • 3.46 kB
import { jsx } from 'react/jsx-runtime'; import { createContext, useRef, useEffect, useMemo, useContext, useSyncExternalStore, useCallback } from 'react'; import { useRocketLeagueSocket } from './useRocketLeagueSocket.js'; const RLContext = createContext(null); const RLProvider = ({ url = "ws://localhost:49122", children }) => { // 1) pull in every event & helpers from the single socket hook const { events: allEvents, send, readyState, error, } = useRocketLeagueSocket(url); // 2) Use a loose-typed ref to avoid union-vs-intersection complaints const dataRef = useRef({}); const subsRef = useRef({}); // 3) Sync incoming allEvents into dataRef and notify subscribers useEffect(() => { // Force evt to be keyof PayloadStorage for type-narrowing const keys = Object.keys(allEvents); keys.forEach((evt) => { const payload = allEvents[evt]; // write into our record (string index) dataRef.current[evt] = payload; // notify anyone subscribed to this key (subsRef.current[evt] || []).forEach((cb) => cb()); }); }, [allEvents]); // 4) Build the same Store API, casting when reading back const store = useMemo(() => ({ getSnapshot: (eventName) => // cast from any back to the correct PayloadStorage type dataRef.current[eventName], subscribe: (eventName, callback) => { const arr = (subsRef.current[eventName] ??= []); arr.push(callback); return () => { subsRef.current[eventName] = arr.filter((c) => c !== callback); }; }, send, readyState, error, }), [send, readyState, error]); return jsx(RLContext.Provider, { value: store, children: children }); }; /** * Subscribe to the full payload of an event. */ function useEvent(eventName) { const store = useContext(RLContext); if (!store) throw new Error("useEvent must be inside <RLProvider>"); return useSyncExternalStore((cb) => store.subscribe(eventName, cb), () => store.getSnapshot(eventName)); } /** Tiny deep-equal for selector comparisons */ function deepEqual(a, b) { if (Object.is(a, b)) return true; if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) { return false; } const aKeys = Object.keys(a), bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) return false; return aKeys.every((key) => deepEqual(a[key], b[key])); } /** * Subscribe to a selected slice of the event payload. Re-renders only on actual changes. */ function useEventSelector(eventName, selector, isEqual = deepEqual) { const store = useContext(RLContext); const lastRef = useRef({}); const subscribe = useCallback((cb) => store.subscribe(eventName, cb), [eventName, store]); const getSnapshot = useCallback(() => { const raw = store.getSnapshot(eventName); const next = selector(raw); if (lastRef.current.val !== undefined && isEqual(next, lastRef.current.val)) { return lastRef.current.val; } lastRef.current.val = next; return next; }, [eventName, selector, store, isEqual]); return useSyncExternalStore(subscribe, getSnapshot); } export { RLContext, RLProvider, useEvent, useEventSelector }; //# sourceMappingURL=RLProvider.js.map