import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs' import { tmpdir } from 'os' import { dirname, join, resolve } from 'path' const INSPECT_NOTICE_STATE_VERSION = 1 as const const INSPECT_NOTICE_PATH_ENV = 'SOOTSIM_INSPECT_NOTICE_PATH' const INSPECT_NOTICE_ENTRY_TTL_MS = 5 * 60 * 1000 export const INSPECT_NOTICE_COOLDOWN_MS = 15_000 type InspectNoticeKind = 'console' | 'requests' interface InspectNoticeEntry { signature: string updatedAt: number } interface InspectNoticeState { version: 1 entries: Record } export function getInspectNoticeStatePath() { return resolve( process.env[INSPECT_NOTICE_PATH_ENV] || join(tmpdir(), 'rnx-inspect-notice-state.json'), ) } function pruneEntries( entries: Record, nowMs: number, ): Record { return Object.fromEntries( Object.entries(entries).filter(([, entry]) => { return ( typeof entry?.signature === 'string' && Number.isFinite(entry?.updatedAt) && nowMs - entry.updatedAt <= INSPECT_NOTICE_ENTRY_TTL_MS ) }), ) } function readInspectNoticeState(nowMs: number): InspectNoticeState { const filepath = getInspectNoticeStatePath() if (!existsSync(filepath)) { return { version: INSPECT_NOTICE_STATE_VERSION, entries: {} } } try { const parsed = JSON.parse( readFileSync(filepath, 'utf8'), ) as Partial if ( parsed.version !== INSPECT_NOTICE_STATE_VERSION || !parsed.entries || typeof parsed.entries !== 'object' ) { rmSync(filepath, { force: true }) return { version: INSPECT_NOTICE_STATE_VERSION, entries: {} } } return { version: INSPECT_NOTICE_STATE_VERSION, entries: pruneEntries(parsed.entries as Record, nowMs), } } catch { rmSync(filepath, { force: true }) return { version: INSPECT_NOTICE_STATE_VERSION, entries: {} } } } function writeInspectNoticeState(state: InspectNoticeState) { const filepath = getInspectNoticeStatePath() mkdirSync(dirname(filepath), { recursive: true }) writeFileSync(filepath, JSON.stringify(state, null, 2) + '\n') } function getInspectNoticeKey(kind: InspectNoticeKind, scope: string) { const normalizedScope = scope.trim() || 'default' return `${kind}:${normalizedScope}` } export function shouldPrintInspectNotice( kind: InspectNoticeKind, scope: string, signature: string, opts: { nowMs?: number; cooldownMs?: number } = {}, ) { const nowMs = opts.nowMs ?? Date.now() const cooldownMs = opts.cooldownMs ?? INSPECT_NOTICE_COOLDOWN_MS const state = readInspectNoticeState(nowMs) const key = getInspectNoticeKey(kind, scope) const previous = state.entries[key] if ( previous && previous.signature === signature && nowMs - previous.updatedAt < cooldownMs ) { return false } state.entries[key] = { signature, updatedAt: nowMs, } writeInspectNoticeState(state) return true } export function clearInspectNoticeState() { rmSync(getInspectNoticeStatePath(), { force: true }) }