bake canonical build harness (deterministic /ship for forks) [landing-page]
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// useChannel + usePresence + useHistory — pre-built OddSockets realtime hooks
|
||||
// ---------------------------------------------------------------------------
|
||||
// PRE-INSTALLED in every React project (realtime leg of the tyga backend
|
||||
// bundle). Same stub->real-client-on-connect pattern as use-anydb/use-tygarun:
|
||||
//
|
||||
// import { useChannel, usePresence, useHistory } from "@/hooks/use-oddsockets"
|
||||
// const { messages, publish, connected } = useChannel("room:lobby")
|
||||
// const { members, updateState } = usePresence("room:lobby")
|
||||
// const { history, loading } = useHistory("room:lobby", { count: 50 })
|
||||
//
|
||||
// Until OddSockets is connected, hooks run against the local demo bus in
|
||||
// lib/oddsockets.ts so realtime UIs render live. `connected` reflects
|
||||
// isConfigured() and `error` surfaces a broken real connect — the demo state
|
||||
// never silently masks a bad key.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
|
||||
import oddsockets, { isConfigured, type ChannelMessage, type PresenceMember } from "@/lib/oddsockets";
|
||||
|
||||
interface ChannelResult {
|
||||
messages: ChannelMessage[];
|
||||
publish: (data: any) => Promise<void>;
|
||||
connected: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a realtime channel. Accumulates incoming messages and exposes a
|
||||
* publish() for sending. `connected` is true only when OddSockets is really
|
||||
* connected (false in demo mode).
|
||||
*/
|
||||
export function useChannel(channelName: string): ChannelResult {
|
||||
const [messages, setMessages] = useState<ChannelMessage[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const channelRef = useRef(oddsockets.channel(channelName));
|
||||
|
||||
useEffect(() => {
|
||||
const ch = oddsockets.channel(channelName);
|
||||
channelRef.current = ch;
|
||||
let off = () => {};
|
||||
try {
|
||||
off = ch.subscribe((msg) => setMessages((prev) => [...prev, msg]));
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to subscribe");
|
||||
console.error(`[useChannel] ${channelName}:`, err);
|
||||
}
|
||||
return () => {
|
||||
try { off(); } catch { /* noop */ }
|
||||
};
|
||||
}, [channelName]);
|
||||
|
||||
const publish = useCallback(async (data: any) => {
|
||||
setError(null);
|
||||
try {
|
||||
await channelRef.current.publish(data);
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to publish");
|
||||
throw err;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { messages, publish, connected: isConfigured(), error };
|
||||
}
|
||||
|
||||
interface PresenceResult {
|
||||
members: PresenceMember[];
|
||||
updateState: (state: Record<string, any>) => Promise<void>;
|
||||
connected: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track presence on a channel (who's here + their state), with updateState()
|
||||
* to publish this client's own state (e.g. { typing: true }).
|
||||
*/
|
||||
export function usePresence(channelName: string): PresenceResult {
|
||||
const [members, setMembers] = useState<PresenceMember[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const channelRef = useRef(oddsockets.channel(channelName));
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
const list = await channelRef.current.getPresence();
|
||||
setMembers(list || []);
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to read presence");
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const ch = oddsockets.channel(channelName);
|
||||
channelRef.current = ch;
|
||||
let off = () => {};
|
||||
try {
|
||||
// re-read presence on every channel message (join/leave/state changes)
|
||||
off = ch.subscribe(() => { refresh(); });
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to subscribe to presence");
|
||||
}
|
||||
refresh();
|
||||
return () => {
|
||||
try { off(); } catch { /* noop */ }
|
||||
};
|
||||
}, [channelName, refresh]);
|
||||
|
||||
const updateState = useCallback(async (state: Record<string, any>) => {
|
||||
setError(null);
|
||||
try {
|
||||
await channelRef.current.updateState(state);
|
||||
await refresh();
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to update state");
|
||||
throw err;
|
||||
}
|
||||
}, [refresh]);
|
||||
|
||||
return { members, updateState, connected: isConfigured(), error };
|
||||
}
|
||||
|
||||
interface HistoryResult {
|
||||
history: ChannelMessage[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch recent messages for a channel (backlog), with loading/error states.
|
||||
*/
|
||||
export function useHistory(
|
||||
channelName: string,
|
||||
options: { count?: number; start?: string; end?: string } = {}
|
||||
): HistoryResult {
|
||||
const { count = 50, start, end } = options;
|
||||
const [history, setHistory] = useState<ChannelMessage[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetchHistory = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const ch = oddsockets.channel(channelName);
|
||||
const result = await ch.getHistory({ count, start, end });
|
||||
setHistory(result || []);
|
||||
} catch (err: any) {
|
||||
setError(err.message || "Failed to fetch history");
|
||||
console.error(`[useHistory] ${channelName}:`, err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [channelName, count, start, end]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchHistory();
|
||||
}, [fetchHistory]);
|
||||
|
||||
return { history, loading, error, refetch: fetchHistory };
|
||||
}
|
||||
Reference in New Issue
Block a user