Every catch now either surfaces, re-throws, or calls logError with a scope tag. No more empty catches or bare console.error: - auth-store: signInToFirebase / restoreSession / signOut paths gain logError context. Behavior is unchanged (best-effort local sign-out, fall back to login on restore failure). - use-stream-autoplay: Audio.play() and download-URL fetches log their failures instead of dropping silently (both are nice-to-haves so UX stays silent — but we can now trace "why didn't autoplay trigger?"). - pusher-client: ws errors / parse failures / server errors / listener crashes all routed through logError, and listener bugs (which silently break user flows) now go through reportError so they're actually surfaced in observability. - settings-page: email-notifications toggle now toasts on failure instead of silently reverting with no explanation. - huddle-app: screen-share failures use logError.
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { useEffect, useRef } from "react";
|
|
import beepSound from "../../assets/sound.wav";
|
|
import type { Network, Particle, StreamProperties } from "@/api/types";
|
|
import { apiClient } from "@/api/client";
|
|
import { useAuthStore } from "@/stores/auth-store";
|
|
import { useAutoplayStore } from "@/stores/autoplay-store";
|
|
import { resolveHumanDisplay } from "@/lib/humans";
|
|
import { logError } from "@/lib/errors";
|
|
|
|
/**
|
|
* Triggers autoplay when a stream's latest child changes to a new media particle.
|
|
* Plays a beep sound for new text particles from other users.
|
|
* Sends autoplay data to the separate autoplay window via IPC.
|
|
*/
|
|
export function useStreamAutoplay(
|
|
latestChild: Particle | null,
|
|
streamParticle: Particle & { type: "stream"; properties: StreamProperties },
|
|
networkId: string,
|
|
network: Network | undefined,
|
|
) {
|
|
const userId = useAuthStore((s) => s.user?.id) ?? "";
|
|
const settledIdRef = useRef<string | undefined>(undefined);
|
|
|
|
useEffect(() => {
|
|
if (!latestChild) return;
|
|
|
|
// First real value: record as baseline, don't autoplay
|
|
if (settledIdRef.current === undefined) {
|
|
settledIdRef.current = latestChild.id;
|
|
return;
|
|
}
|
|
|
|
if (latestChild.id === settledIdRef.current) return;
|
|
settledIdRef.current = latestChild.id;
|
|
|
|
if (latestChild.created_by_human_id === userId) return;
|
|
|
|
if (useAutoplayStore.getState().muted) return;
|
|
|
|
if (latestChild.type === "text") {
|
|
// Browser autoplay policy can block this before user interaction; that's
|
|
// fine — the beep is a nice-to-have, not a critical signal.
|
|
new Audio(beepSound).play().catch((err) =>
|
|
logError(err, { scope: "autoplay.beep" }),
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (latestChild.type !== "media") return;
|
|
|
|
const particle = latestChild;
|
|
const { displayName, initials } = resolveHumanDisplay(
|
|
particle.created_by_human_id,
|
|
network?.humans,
|
|
);
|
|
|
|
apiClient.getParticleDownloadUrl(particle.properties.object_id).then((downloadUrl) => {
|
|
window.electronAutoplay.play({
|
|
particleId: particle.id,
|
|
streamId: streamParticle.id,
|
|
networkId,
|
|
downloadUrl,
|
|
mimeType: particle.properties.mime_type,
|
|
durationMs: particle.properties.duration_ms,
|
|
senderName: displayName,
|
|
senderInitials: initials,
|
|
});
|
|
}).catch((err) =>
|
|
logError(err, { scope: "autoplay.fetchUrl", particleId: particle.id }),
|
|
);
|
|
}, [latestChild?.id]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
}
|