mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): stop marking users away while active on the machine (#1942)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Generated
+130
-1
@@ -2,6 +2,27 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "CoreFoundation-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0e9889e6db118d49d88d84728d0e964d973a5680befb5f85f55141beea5c20b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mach 0.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "IOKit-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99696c398cbaf669d2368076bdb3d627fb0ce51a26899d7c61228c5c0af3bf4a"
|
||||
dependencies = [
|
||||
"CoreFoundation-sys",
|
||||
"libc",
|
||||
"mach 0.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
@@ -1021,6 +1042,7 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"toml 0.8.2",
|
||||
"url",
|
||||
"user-idle",
|
||||
"uuid",
|
||||
"window-vibrancy",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -1836,6 +1858,16 @@ dependencies = [
|
||||
"syn 2.0.118",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cstr"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.8.0"
|
||||
@@ -4690,6 +4722,24 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.5.0"
|
||||
@@ -5925,7 +5975,7 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||
dependencies = [
|
||||
"proc-macro-crate 2.0.2",
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.118",
|
||||
@@ -10890,6 +10940,19 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "user-idle"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433621584802937d26ab1b490236d802a9bc6d7176fe913cc12c2e51a790d5a2"
|
||||
dependencies = [
|
||||
"CoreFoundation-sys",
|
||||
"IOKit-sys",
|
||||
"cstr",
|
||||
"mach 0.3.2",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
@@ -11683,6 +11746,15 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -11734,6 +11806,21 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -11800,6 +11887,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -11818,6 +11911,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -11836,6 +11935,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -11866,6 +11971,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -11884,6 +11995,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -11902,6 +12019,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -11920,6 +12043,12 @@ version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
||||
@@ -46,10 +46,12 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = ["NSHa
|
||||
keyring = { version = "3.6.3", default-features = false, features = ["apple-native", "vendored"], optional = true }
|
||||
security-framework = { version = "3.7.0", features = ["OSX_10_15"] }
|
||||
window-vibrancy = "0.6"
|
||||
user-idle = { version = "0.6", default-features = false }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.61", features = ["Win32_Security", "Win32_Storage_FileSystem", "Win32_System_JobObjects", "Win32_System_Registry", "Win32_System_Threading", "Win32_Foundation"] }
|
||||
keyring = { version = "3.6.3", default-features = false, features = ["windows-native", "vendored"], optional = true }
|
||||
user-idle = { version = "0.6", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
atomic-write-file = "0.3"
|
||||
|
||||
@@ -27,6 +27,7 @@ pub(crate) mod mesh_llm;
|
||||
mod messages;
|
||||
mod notifications;
|
||||
mod observer_archive;
|
||||
mod os_idle;
|
||||
pub mod pairing;
|
||||
mod personas;
|
||||
mod prevent_sleep;
|
||||
@@ -72,6 +73,7 @@ pub use mesh_llm::*;
|
||||
pub use messages::*;
|
||||
pub use notifications::*;
|
||||
pub use observer_archive::*;
|
||||
pub use os_idle::*;
|
||||
pub use pairing::*;
|
||||
pub use personas::*;
|
||||
pub use prevent_sleep::*;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/// Seconds since the last OS-wide user input (keyboard/mouse anywhere on the
|
||||
/// machine), or `None` on platforms without a supported idle API (Linux
|
||||
/// Wayland). Callers fall back to in-app activity tracking when `None`.
|
||||
#[tauri::command]
|
||||
pub fn get_os_idle_seconds() -> Option<u64> {
|
||||
#[cfg(any(target_os = "macos", windows))]
|
||||
{
|
||||
user_idle::UserIdle::get_time()
|
||||
.ok()
|
||||
.map(|idle| idle.as_seconds())
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -760,6 +760,7 @@ pub fn run() {
|
||||
open_project_terminal,
|
||||
search_users,
|
||||
get_presence,
|
||||
get_os_idle_seconds,
|
||||
get_default_relay_url,
|
||||
get_legacy_workspace_storage,
|
||||
is_shared_identity,
|
||||
|
||||
@@ -2,18 +2,20 @@ import * as React from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { getOsIdleSeconds } from "@/shared/api/osIdle";
|
||||
import { getPresence } from "@/shared/api/tauri";
|
||||
import { normalizePubkey } from "@/shared/lib/pubkey";
|
||||
import {
|
||||
mergePresenceUpdate,
|
||||
parseLivePresenceEvent,
|
||||
presenceQueryWantsPubkey,
|
||||
resolveAutomaticPresenceStatus,
|
||||
} from "@/features/presence/lib/presence";
|
||||
import type { PresenceLookup, PresenceStatus } from "@/shared/api/types";
|
||||
|
||||
const PRESENCE_HEARTBEAT_INTERVAL_MS = 30_000;
|
||||
const PRESENCE_IDLE_TIMEOUT_MS = 5 * 60_000;
|
||||
const PRESENCE_STATUS_TICK_INTERVAL_MS = 30_000;
|
||||
const PRESENCE_ACTIVITY_THROTTLE_MS = 1_000;
|
||||
const PRESENCE_TTL_SECONDS = 90;
|
||||
const PRESENCE_PREFERENCE_STORAGE_KEY = "buzz-presence-preference";
|
||||
|
||||
@@ -62,16 +64,11 @@ function writeStoredPresencePreference(
|
||||
window.localStorage.setItem(presencePreferenceStorageKey(pubkey), preference);
|
||||
}
|
||||
|
||||
function resolveAutomaticPresenceStatus(
|
||||
isDocumentHidden: boolean,
|
||||
function resolveAutomaticPresenceStatusSync(
|
||||
lastActivityAt: number,
|
||||
now: number,
|
||||
): PresenceStatus {
|
||||
if (isDocumentHidden) {
|
||||
return "away";
|
||||
}
|
||||
|
||||
return now - lastActivityAt >= PRESENCE_IDLE_TIMEOUT_MS ? "away" : "online";
|
||||
return resolveAutomaticPresenceStatus(null, lastActivityAt, now);
|
||||
}
|
||||
|
||||
export function usePresenceQuery(
|
||||
@@ -208,27 +205,43 @@ export function usePresenceSession(pubkey?: string) {
|
||||
const lastActivityAtRef = React.useRef(Date.now());
|
||||
const [automaticStatus, setAutomaticStatus] = React.useState<PresenceStatus>(
|
||||
() =>
|
||||
resolveAutomaticPresenceStatus(
|
||||
typeof document === "undefined" ? false : document.hidden,
|
||||
lastActivityAtRef.current,
|
||||
Date.now(),
|
||||
),
|
||||
resolveAutomaticPresenceStatusSync(lastActivityAtRef.current, Date.now()),
|
||||
);
|
||||
const automaticStatusRef = React.useRef(automaticStatus);
|
||||
const skipNextSyncRef = React.useRef<PresenceStatus | null>(null);
|
||||
|
||||
const reevaluateAutomaticStatus = React.useEffectEvent(() => {
|
||||
const next = resolveAutomaticPresenceStatus(
|
||||
typeof document === "undefined" ? false : document.hidden,
|
||||
lastActivityAtRef.current,
|
||||
Date.now(),
|
||||
);
|
||||
const applyAutomaticStatus = React.useEffectEvent((next: PresenceStatus) => {
|
||||
if (next !== automaticStatusRef.current) {
|
||||
automaticStatusRef.current = next;
|
||||
setAutomaticStatus(next);
|
||||
}
|
||||
});
|
||||
|
||||
const reevaluateAutomaticStatus = React.useEffectEvent(() => {
|
||||
applyAutomaticStatus(
|
||||
resolveAutomaticPresenceStatusSync(lastActivityAtRef.current, Date.now()),
|
||||
);
|
||||
});
|
||||
|
||||
// OS-wide idle is authoritative when available; async, so tick-driven only.
|
||||
const reevaluateFromOsIdle = React.useEffectEvent(async () => {
|
||||
const osIdleSeconds = await getOsIdleSeconds().catch(() => null);
|
||||
if (typeof osIdleSeconds === "number") {
|
||||
// Keep the fallback clock consistent so a later null reading can't flap.
|
||||
lastActivityAtRef.current = Math.max(
|
||||
lastActivityAtRef.current,
|
||||
Date.now() - osIdleSeconds * 1000,
|
||||
);
|
||||
}
|
||||
applyAutomaticStatus(
|
||||
resolveAutomaticPresenceStatus(
|
||||
typeof osIdleSeconds === "number" ? osIdleSeconds : null,
|
||||
lastActivityAtRef.current,
|
||||
Date.now(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
setPresencePreference(readStoredPresencePreference(normalizedPubkey));
|
||||
lastActivityAtRef.current = Date.now();
|
||||
@@ -249,37 +262,36 @@ export function usePresenceSession(pubkey?: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback activity signal: wheel/pointermove count (passive reading),
|
||||
// throttled to 1/s; window visibility never affects presence.
|
||||
let lastRecordedAt = 0;
|
||||
|
||||
function handleUserActivity() {
|
||||
if (typeof document !== "undefined" && document.hidden) {
|
||||
const now = Date.now();
|
||||
if (now - lastRecordedAt < PRESENCE_ACTIVITY_THROTTLE_MS) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastRecordedAt = now;
|
||||
recordActivity();
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
recordActivity();
|
||||
}
|
||||
|
||||
function handleVisibilityChange() {
|
||||
if (!document.hidden) {
|
||||
recordActivity();
|
||||
return;
|
||||
}
|
||||
// Going hidden can flip the automatic status to away.
|
||||
reevaluateAutomaticStatus();
|
||||
}
|
||||
|
||||
window.addEventListener("pointerdown", handleUserActivity, true);
|
||||
window.addEventListener("pointermove", handleUserActivity, true);
|
||||
window.addEventListener("wheel", handleUserActivity, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
window.addEventListener("keydown", handleUserActivity, true);
|
||||
window.addEventListener("focus", handleFocus);
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
window.addEventListener("focus", handleUserActivity);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("pointerdown", handleUserActivity, true);
|
||||
window.removeEventListener("pointermove", handleUserActivity, true);
|
||||
window.removeEventListener("wheel", handleUserActivity, {
|
||||
capture: true,
|
||||
});
|
||||
window.removeEventListener("keydown", handleUserActivity, true);
|
||||
window.removeEventListener("focus", handleFocus);
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
window.removeEventListener("focus", handleUserActivity);
|
||||
};
|
||||
}, [normalizedPubkey]);
|
||||
|
||||
@@ -288,8 +300,9 @@ export function usePresenceSession(pubkey?: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
void reevaluateFromOsIdle();
|
||||
const intervalId = window.setInterval(() => {
|
||||
reevaluateAutomaticStatus();
|
||||
void reevaluateFromOsIdle();
|
||||
}, PRESENCE_STATUS_TICK_INTERVAL_MS);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
mergePresenceUpdate,
|
||||
parseLivePresenceEvent,
|
||||
presenceQueryWantsPubkey,
|
||||
PRESENCE_IDLE_TIMEOUT_MS,
|
||||
resolveAutomaticPresenceStatus,
|
||||
} from "./presence.ts";
|
||||
|
||||
const WILL = "8e39cba681211b3782d0e4483e9343719b9b7be66515252da5491f26421896b1";
|
||||
@@ -77,3 +79,37 @@ test("live event lowercases the author pubkey", () => {
|
||||
});
|
||||
assert.equal(parsed.pubkey, WILL);
|
||||
});
|
||||
|
||||
test("OS idle below the threshold is online even with stale in-app activity", () => {
|
||||
const now = 1_000_000_000;
|
||||
const staleActivity = now - 60 * 60_000;
|
||||
assert.equal(
|
||||
resolveAutomaticPresenceStatus(30, staleActivity, now),
|
||||
"online",
|
||||
);
|
||||
});
|
||||
|
||||
test("OS idle at the threshold is away even with fresh in-app activity", () => {
|
||||
const now = 1_000_000_000;
|
||||
assert.equal(resolveAutomaticPresenceStatus(600, now, now), "away");
|
||||
});
|
||||
|
||||
test("without OS idle, fresh in-app activity is online", () => {
|
||||
const now = 1_000_000_000;
|
||||
assert.equal(
|
||||
resolveAutomaticPresenceStatus(null, now - 1_000, now),
|
||||
"online",
|
||||
);
|
||||
});
|
||||
|
||||
test("without OS idle, in-app inactivity past the threshold is away", () => {
|
||||
const now = 1_000_000_000;
|
||||
assert.equal(
|
||||
resolveAutomaticPresenceStatus(null, now - PRESENCE_IDLE_TIMEOUT_MS, now),
|
||||
"away",
|
||||
);
|
||||
});
|
||||
|
||||
test("OS idle of zero is online", () => {
|
||||
assert.equal(resolveAutomaticPresenceStatus(0, 0, 1_000_000_000), "online");
|
||||
});
|
||||
|
||||
@@ -36,6 +36,22 @@ export function mergePresenceUpdate(
|
||||
return { ...old, [pubkey]: status };
|
||||
}
|
||||
|
||||
// Away means "human not at the machine" (Slack/Discord semantics), never
|
||||
// "Buzz is not the focused window". OS-wide idle is authoritative when the
|
||||
// platform exposes it; otherwise fall back to in-app activity.
|
||||
export const PRESENCE_IDLE_TIMEOUT_MS = 10 * 60_000;
|
||||
|
||||
export function resolveAutomaticPresenceStatus(
|
||||
osIdleSeconds: number | null,
|
||||
lastActivityAt: number,
|
||||
now: number,
|
||||
): PresenceStatus {
|
||||
if (osIdleSeconds !== null) {
|
||||
return osIdleSeconds * 1000 >= PRESENCE_IDLE_TIMEOUT_MS ? "away" : "online";
|
||||
}
|
||||
return now - lastActivityAt >= PRESENCE_IDLE_TIMEOUT_MS ? "away" : "online";
|
||||
}
|
||||
|
||||
export function getPresenceLabel(status: PresenceStatus) {
|
||||
switch (status) {
|
||||
case "online":
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { invokeTauri } from "@/shared/api/tauri";
|
||||
|
||||
// Seconds since the last OS-wide user input, or null where the platform has
|
||||
// no supported idle API (e.g. Linux Wayland).
|
||||
export function getOsIdleSeconds(): Promise<number | null> {
|
||||
return invokeTauri<number | null>("get_os_idle_seconds");
|
||||
}
|
||||
@@ -8619,6 +8619,10 @@ export function maybeInstallE2eTauriMocks() {
|
||||
},
|
||||
activeConfig,
|
||||
);
|
||||
case "get_os_idle_seconds":
|
||||
// e2e runs headless with no OS idle API; the presence hook falls back
|
||||
// to in-app activity tracking.
|
||||
return null;
|
||||
case "get_git_identity":
|
||||
// Matches the "Thomas P" author on a mock snapshot commit so the
|
||||
// viewer-identity avatar attribution is exercised in e2e.
|
||||
|
||||
Reference in New Issue
Block a user