Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions desktop/src/features/agents/lib/useAgentsDataRefresh.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isTauri } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
Expand Down Expand Up @@ -26,6 +27,11 @@ export function useAgentsDataRefresh(): void {
const queryClient = useQueryClient();

useEffect(() => {
// Both events come from the local Rust agents backend — no on-disk agents
// data exists in a browser (web) runtime, and `listen()` throws without
// Tauri internals.
if (!isTauri()) return;

let timer: ReturnType<typeof setTimeout> | undefined;

const unlistenRuntime = listen("managed-agent-runtime-status", () => {
Expand Down
5 changes: 5 additions & 0 deletions desktop/src/features/agents/usePreventSleep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { createPreventSleepActivityTracker } from "@/features/agents/preventSleepActivity";
import { setPreventSleepActive } from "@/shared/api/tauri";
import { normalizePubkey } from "@/shared/lib/pubkey";
import { isTauri } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";

// Intentionally not scoped per-pubkey — multi-user desktop is rare and the
Expand Down Expand Up @@ -85,6 +86,10 @@ function usePreventSleepInternal() {
void setPreventSleepActive(active);
}, [active]);
React.useEffect(() => {
// Sleep prevention is a native OS concept — no such event exists in a
// browser (web) runtime, and `listen()` throws without Tauri internals.
if (!isTauri()) return;

const unlisten = listen("prevent-sleep-expired", () => {
setExpired(true);
});
Expand Down
8 changes: 8 additions & 0 deletions desktop/src/features/communities/legacyCommunityStorage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isTauri } from "@tauri-apps/api/core";

import { invokeTauri } from "@/shared/api/tauri";
import { migrateLegacyCommunityStorage } from "./communityStorage";

Expand Down Expand Up @@ -115,6 +117,12 @@ export async function migrateLegacyCommunityStorageBeforeRender(): Promise<void>
return;
}

// The legacy Sprout WebKit SQLite database only exists in the native shell
// — there is nothing to migrate from in a browser (web) runtime.
if (!isTauri()) {
return;
}

migrateLegacyCommunityStorage(window.localStorage);
const currentCommunitiesRaw =
window.localStorage.getItem(BUZZ_COMMUNITIES_KEY);
Expand Down
5 changes: 5 additions & 0 deletions desktop/src/features/communities/useNestNotifications.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isTauri } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { useEffect } from "react";
import { toast } from "sonner";
Expand All @@ -22,6 +23,10 @@ const MIGRATION_TOAST_KEY = "buzz-legacy-nest-migrated-notified";
*/
export function useNestNotifications(): void {
useEffect(() => {
// Both events come from the Rust nest backend — no local nest exists in a
// browser (web) runtime, and `listen()` throws without Tauri internals.
if (!isTauri()) return;

const unlistenReposError = listen<string>("repos-dir-error", (event) => {
toast.error("Repos directory not applied", {
description: event.payload,
Expand Down
6 changes: 5 additions & 1 deletion desktop/src/features/huddle/HuddleContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { invoke } from "@tauri-apps/api/core";
import { invoke, isTauri } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import * as React from "react";

Expand Down Expand Up @@ -616,6 +616,10 @@ export function HuddleProvider({ children }: { children: React.ReactNode }) {
// in-flight guard collapses duplicate disconnect events from failed dials.
const audioReconnectInFlightRef = React.useRef(false);
React.useEffect(() => {
// Huddle audio is a native Tauri pipeline — no such event exists in a
// browser (web) runtime, and `listen()` throws without Tauri internals.
if (!isTauri()) return;

let cancelled = false;
let unlisten: (() => void) | null = null;
listen("huddle-audio-disconnected", () => {
Expand Down
7 changes: 6 additions & 1 deletion desktop/src/features/huddle/components/HuddleBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { invoke } from "@tauri-apps/api/core";
import { invoke, isTauri } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import {
Bot,
Expand Down Expand Up @@ -216,6 +216,11 @@ export function HuddleBar({
}, []);
// Huddle state: event-driven + slow fallback poll.
React.useEffect(() => {
// The huddle backend (state + audio pipeline) is native Tauri-only — none
// of this works in a browser (web) runtime, and `listen()` throws without
// Tauri internals.
if (!isTauri()) return;

let cancelled = false;
let unlisten: (() => void) | null = null;

Expand Down
6 changes: 6 additions & 0 deletions desktop/src/features/huddle/components/HuddleIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isTauri } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { Headphones } from "lucide-react";
import * as React from "react";
Expand Down Expand Up @@ -190,6 +191,11 @@ export function HuddleIndicator({
// waiting for the relay's 48103 event (which may arrive late or not at all
// if the relay connection tears down first).
React.useEffect(() => {
// Huddle state is emitted by the native Tauri audio pipeline — no such
// event exists in a browser (web) runtime, and `listen()` throws without
// Tauri internals.
if (!isTauri()) return;

let unlisten: (() => void) | null = null;
let cancelled = false;

Expand Down
8 changes: 8 additions & 0 deletions desktop/src/features/settings/ui/MobilePairingCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
TriangleAlert,
X,
} from "lucide-react";
import { isTauri } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { toast } from "sonner";

Expand Down Expand Up @@ -220,6 +221,13 @@ export function MobilePairingCard({
return;
}

// Mobile pairing is a native Tauri feature (QR/SAS handshake with a
// phone) — none of these events exist in a browser (web) runtime, and
// `listen()` throws without Tauri internals.
if (!isTauri()) {
return;
}

let cancelled = false;
const unlisteners: (() => void)[] = [];

Expand Down
21 changes: 19 additions & 2 deletions desktop/src/shared/api/tauri.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { invoke as tauriInvoke } from "@tauri-apps/api/core";
import { invoke as tauriInvoke, isTauri } from "@tauri-apps/api/core";
import {
activateRateLimit,
parseRateLimitHint,
Expand Down Expand Up @@ -366,15 +366,26 @@ export async function getPresence(pubkeys: string[]): Promise<PresenceLookup> {
);
}

// Called unconditionally on every boot by useCommunityInit's "no active
// community yet" branch, which already wraps both calls in its own
// try/catch and falls back to needsSetup:true — so this guard is a
// console-noise reduction, not a behavior fix.
export function getDefaultRelayUrl(): Promise<string> {
if (!isTauri()) return Promise.reject(new Error("no Tauri runtime"));
return invokeTauri<string>("get_default_relay_url");
}

export function autoConnectDefaultRelayEnabled(): Promise<boolean> {
if (!isTauri()) return Promise.resolve(false);
return invokeTauri<boolean>("auto_connect_default_relay_enabled");
}

// Called unconditionally at app boot, before any Tauri-availability gate —
// unlike most of this file's exports, which only run once the app has
// already reached a native-only screen. Guard so it resolves cleanly to
// `false` in a browser (web) runtime instead of throwing.
export function isSharedIdentity(): Promise<boolean> {
if (!isTauri()) return Promise.resolve(false);
return invokeTauri<boolean>("is_shared_identity");
}

Expand Down Expand Up @@ -1188,8 +1199,14 @@ export async function validateReposDir(dir: string): Promise<void> {
await invokeTauri("validate_repos_dir", { dir });
}

// Sleep prevention is a native OS concept with no browser equivalent.
// usePreventSleep.ts calls this fire-and-forget (`void setPreventSleepActive(...)`)
// with no .catch of its own, so an unguarded call here was a genuine unhandled
// promise rejection in a web (non-Tauri) runtime — not just console noise.
export const setPreventSleepActive = (active: boolean) =>
invokeTauri("set_prevent_sleep_active", { active });
isTauri()
? invokeTauri("set_prevent_sleep_active", { active })
: Promise.resolve();

export const setAgentManagedProfiles = (enabled: boolean) =>
invokeTauri("set_agent_managed_profiles", { enabled });
Expand Down
22 changes: 21 additions & 1 deletion desktop/src/shared/deep-link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import { invoke, isTauri } from "@tauri-apps/api/core";
import type { StartCommunityOnboardingInput } from "@/features/onboarding/communityOnboarding";

export type AddCommunityDeepLinkPayload = {
Expand Down Expand Up @@ -84,6 +84,11 @@ function acceptPendingCommunityDeepLink(
}

async function drainPendingCommunityDeepLinks(deps: DeepLinkDeps) {
// The pending-deep-link queue is populated by the Rust backend and only
// exists in the native Tauri shell — invoke() throws without Tauri
// internals in a browser (web) runtime.
if (!isTauri()) return;

while (true) {
const pending = await invoke<PendingCommunityDeepLink | null>(
"take_pending_community_deep_link",
Expand Down Expand Up @@ -134,6 +139,17 @@ export async function listenForDeepLinks(
})();
};
const stopAvailabilityListener = deps.onAddCommunityAvailable(drain);

// OS-level deep links (`buzz://…`) only exist in the native Tauri shell —
// `listen()` throws without Tauri internals in a browser (web) runtime.
// The in-app "add community available" wiring above still applies.
if (!isTauri()) {
drain();
return () => {
stopAvailabilityListener();
};
}

const connectPromise = listen<string>("deep-link-connect", drain);
const joinPromise = listen<JoinDeepLinkPayload>("deep-link-join", drain);
const addCommunityPromise = listen<AddCommunityDeepLinkPayload>(
Expand All @@ -160,6 +176,8 @@ export async function listenForDeepLinks(
export function listenForMessageDeepLinks(
onOpen: (payload: MessageDeepLinkPayload) => void,
): Promise<UnlistenFn> {
// OS-level deep link — no-op outside the native Tauri shell.
if (!isTauri()) return Promise.resolve(() => {});
return listen<MessageDeepLinkPayload>("deep-link-message", (event) => {
onOpen(event.payload);
});
Expand All @@ -168,6 +186,8 @@ export function listenForMessageDeepLinks(
export function listenForNostrBindDeepLinks(
onOpen: (payload: NostrBindDeepLinkPayload) => void,
): Promise<UnlistenFn> {
// OS-level deep link — no-op outside the native Tauri shell.
if (!isTauri()) return Promise.resolve(() => {});
return listen<NostrBindDeepLinkPayload>("deep-link-nostr-bind", (event) => {
onOpen(event.payload);
});
Expand Down