mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 08:01:56 +00:00
The six oversized src/main/ipc modules each carried a file-level `eslint-disable max-lines` and ran 427-671 counted lines against a 300-line budget. AGENTS.md calls for splitting rather than suppressing, and config/max-lines-baseline.txt is a shrink-only ratchet, so this removes all six suppressions and prunes their entries (341 -> 335). Pure move, no behavior change. Each file is cut along the seams it already had: pet splits into format allowlist / storage paths / symlink-safe copy / bundle manifest + import; filesystem-auth into path-containment primitives, the config-derived allow-list, and the git-registered root cache; notifications into sound selection, native lifecycle, permission probe, and burst cooldown; crash-reporting into renderer error reports, breadcrumbs, and sender. The IPC surface is proved intact rather than assumed: comparing (method, channel) multisets between HEAD and the split gives 49 registrations across 49 distinct channels on both sides. filesystem-auth's security boundary keeps its acyclic layering -- containment primitives, then allow-list, then root cache, then path-resolution orchestration -- with no layer gaining a back-edge. Also keeps clipboard-ipc-handlers.test.ts under the 800-line test budget. The split had briefly added a redundant vi.mock for isENOENT (byte-identical to the real implementation) that pushed it to 801; the mock is dropped in favor of the real function, with realpath added to the existing node:fs/promises mock. Verified: oxlint clean, ratchet passes, typecheck clean, full unit suite green (the one remaining failure is a pre-existing load flake in an untouched file, green when re-run serially), no new runtime import cycles among 617 modules, and no lint suppression added anywhere.
86 lines
2.2 KiB
TypeScript
86 lines
2.2 KiB
TypeScript
import type { Notification } from 'electron'
|
|
|
|
const NOTIFICATION_DISPLAY_CONFIRMATION_TIMEOUT_MS = 2500
|
|
const NOTIFICATION_RELEASE_FALLBACK_MS = 5 * 60 * 1000
|
|
|
|
// Why: keep a strong reference so GC can't collect notifications (and their click handlers) before the user interacts with them.
|
|
export const activeNotifications = new Set<Notification>()
|
|
export const activeNotificationsById = new Map<
|
|
string,
|
|
{ notification: Notification; release: () => void }
|
|
>()
|
|
|
|
export function retainNotificationUntilRelease(
|
|
notification: Notification,
|
|
onRelease?: () => void
|
|
): () => void {
|
|
activeNotifications.add(notification)
|
|
let released = false
|
|
let releaseTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
function release(): void {
|
|
if (released) {
|
|
return
|
|
}
|
|
released = true
|
|
activeNotifications.delete(notification)
|
|
notification.removeListener('close', release)
|
|
if (releaseTimer) {
|
|
clearTimeout(releaseTimer)
|
|
releaseTimer = null
|
|
}
|
|
onRelease?.()
|
|
}
|
|
|
|
notification.on('close', release)
|
|
releaseTimer = setTimeout(release, NOTIFICATION_RELEASE_FALLBACK_MS)
|
|
if (typeof releaseTimer.unref === 'function') {
|
|
releaseTimer.unref()
|
|
}
|
|
|
|
return release
|
|
}
|
|
|
|
export function waitForNotificationDisplay(notification: Notification): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
let settled = false
|
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
function cleanup(): void {
|
|
notification.removeListener('show', onShow)
|
|
notification.removeListener('failed', onFailed)
|
|
if (timer) {
|
|
clearTimeout(timer)
|
|
timer = null
|
|
}
|
|
}
|
|
|
|
function settle(displayed: boolean): void {
|
|
if (settled) {
|
|
return
|
|
}
|
|
settled = true
|
|
cleanup()
|
|
resolve(displayed)
|
|
}
|
|
|
|
function onShow(): void {
|
|
settle(true)
|
|
}
|
|
|
|
function onFailed(): void {
|
|
settle(false)
|
|
}
|
|
|
|
notification.once('show', onShow)
|
|
notification.once('failed', onFailed)
|
|
timer = setTimeout(() => settle(false), NOTIFICATION_DISPLAY_CONFIRMATION_TIMEOUT_MS)
|
|
})
|
|
}
|
|
|
|
export function logNativeNotificationFailure(context: string, error?: string): void {
|
|
console.warn(
|
|
`[notifications] ${context} notification failed to show${error ? `: ${error}` : '.'}`
|
|
)
|
|
}
|