111 lines
2.5 KiB
TypeScript
111 lines
2.5 KiB
TypeScript
import {
|
|
createContext,
|
|
createElement,
|
|
type ReactNode,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
} from "react";
|
|
import type {
|
|
PartySocketEvent,
|
|
PartyState,
|
|
} from "../../../api/src/party-types";
|
|
import { usePartySocket } from "./use-party-socket";
|
|
import { useUser } from "./user";
|
|
|
|
const emptyPartyState: PartyState = {
|
|
party: null,
|
|
members: [],
|
|
};
|
|
|
|
type PartyContextValue = PartyState & {
|
|
connectionState: "disconnected" | "connecting" | "connected" | "reconnecting";
|
|
isConnected: boolean;
|
|
isConnecting: boolean;
|
|
isReconnecting: boolean;
|
|
setPartyState: (state: PartyState) => void;
|
|
resetParty: () => void;
|
|
};
|
|
|
|
const PartyContext = createContext<PartyContextValue | null>(null);
|
|
|
|
function reducePartyState(
|
|
state: PartyState,
|
|
event: PartySocketEvent,
|
|
userId: string,
|
|
): PartyState {
|
|
switch (event.type) {
|
|
case "party_status": {
|
|
if (!event.party) return emptyPartyState;
|
|
const isMember = event.members.some((member) => member.userId === userId);
|
|
if (!isMember) return emptyPartyState;
|
|
return { party: event.party, members: event.members };
|
|
}
|
|
case "member_payload":
|
|
case "pong":
|
|
case "error":
|
|
return state;
|
|
}
|
|
}
|
|
|
|
function getApiUrl(): string | null {
|
|
if (typeof window === "undefined") return null;
|
|
const envUrl = import.meta.env.VITE_BETTER_AUTH_URL;
|
|
if (envUrl) return envUrl;
|
|
return `${window.location.protocol}//${window.location.host}`;
|
|
}
|
|
|
|
export function PartyProvider({ children }: { children: ReactNode }) {
|
|
const { user } = useUser();
|
|
const userId = user?.id ?? null;
|
|
const [state, setState] = useState<PartyState>(emptyPartyState);
|
|
|
|
const handleMessage = useCallback(
|
|
(event: PartySocketEvent) => {
|
|
if (!userId) return;
|
|
setState((prev: PartyState) => reducePartyState(prev, event, userId));
|
|
},
|
|
[userId],
|
|
);
|
|
|
|
const apiUrl = useMemo(() => {
|
|
const url = getApiUrl();
|
|
if (!url) return null;
|
|
return url;
|
|
}, []);
|
|
|
|
const resetParty = useCallback(() => {
|
|
setState(emptyPartyState);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!userId) resetParty();
|
|
}, [resetParty, userId]);
|
|
|
|
const wsState = usePartySocket({
|
|
apiUrl: userId ? apiUrl : null,
|
|
onMessage: userId ? handleMessage : null,
|
|
});
|
|
|
|
const value = useMemo(
|
|
() => ({
|
|
...state,
|
|
...wsState,
|
|
setPartyState: setState,
|
|
resetParty,
|
|
}),
|
|
[state, wsState, resetParty],
|
|
);
|
|
|
|
return createElement(PartyContext.Provider, { value }, children);
|
|
}
|
|
|
|
export function useParty() {
|
|
const context = useContext(PartyContext);
|
|
if (!context) {
|
|
throw new Error("useParty must be used within PartyProvider");
|
|
}
|
|
return context;
|
|
}
|