66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import { useEffect } from 'react'
|
|
import {
|
|
getLastInteractionTime,
|
|
updateLastInteractionTime,
|
|
} from '../bootstrap/state.js'
|
|
import { useTerminalNotification } from '../ink/useTerminalNotification.js'
|
|
import { sendNotification } from '../services/notifier.js'
|
|
// The time threshold in milliseconds for considering an interaction "recent" (6 seconds)
|
|
export const DEFAULT_INTERACTION_THRESHOLD_MS = 6000
|
|
|
|
function getTimeSinceLastInteraction(): number {
|
|
return Date.now() - getLastInteractionTime()
|
|
}
|
|
|
|
function hasRecentInteraction(threshold: number): boolean {
|
|
return getTimeSinceLastInteraction() < threshold
|
|
}
|
|
|
|
function shouldNotify(threshold: number): boolean {
|
|
return process.env.NODE_ENV !== 'test' && !hasRecentInteraction(threshold)
|
|
}
|
|
|
|
// NOTE: User interaction tracking is now done in App.tsx's processKeysInBatch
|
|
// function, which calls updateLastInteractionTime() when any input is received.
|
|
// This avoids having a separate stdin 'data' listener that would compete with
|
|
// the main 'readable' listener and cause dropped input characters.
|
|
|
|
/**
|
|
* Hook that manages desktop notifications after a timeout period.
|
|
*
|
|
* Shows a notification in two cases:
|
|
* 1. Immediately if the app has been idle for longer than the threshold
|
|
* 2. After the specified timeout if the user doesn't interact within that time
|
|
*
|
|
* @param message - The notification message to display
|
|
* @param timeout - The timeout in milliseconds (defaults to 6000ms)
|
|
*/
|
|
export function useNotifyAfterTimeout(
|
|
message: string,
|
|
notificationType: string,
|
|
): void {
|
|
const terminal = useTerminalNotification()
|
|
|
|
// Reset interaction time when hook is called to make sure that requests
|
|
// that took a long time to complete don't pop up a notification right away.
|
|
// Must be immediate because useEffect runs after Ink's render cycle has
|
|
// already flushed; without it the timestamp stays stale and a premature
|
|
// notification fires if the user is idle (no subsequent renders to flush).
|
|
useEffect(() => {
|
|
updateLastInteractionTime(true)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
let hasNotified = false
|
|
const timer = setInterval(() => {
|
|
if (shouldNotify(DEFAULT_INTERACTION_THRESHOLD_MS) && !hasNotified) {
|
|
hasNotified = true
|
|
clearInterval(timer)
|
|
void sendNotification({ message, notificationType }, terminal)
|
|
}
|
|
}, DEFAULT_INTERACTION_THRESHOLD_MS)
|
|
|
|
return () => clearInterval(timer)
|
|
}, [message, notificationType, terminal])
|
|
}
|