@@ -50,6 +50,8 @@ function isNewerVersion(current: string, target: string): boolean {
5050}
5151
5252const AUTO_RESCAN_MS = 5 * 60_000 ;
53+ const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60_000 ;
54+ const UPDATE_CHECK_RETRY_MS = 60 * 60_000 ;
5355const CODEX_QUOTA_FORECAST_URL = "https://www.willcodexquotareset.com/" ;
5456const CHATGPT_USAGE_URL = "https://chatgpt.com/#settings/Usage" ;
5557
@@ -249,6 +251,7 @@ export function useUsageDashboard() {
249251 const lastLimitsFetchTimeRef = useRef < number > ( 0 ) ;
250252 const lastAutoScanTimeRef = useRef < number > ( 0 ) ;
251253 const scanInFlightRef = useRef < Promise < void > | null > ( null ) ;
254+ const updateCheckInFlightRef = useRef < Promise < void > | null > ( null ) ;
252255
253256 const loadOverview = useEffectEvent ( async ( nextRange : RangeKey ) => {
254257 const data = await fetchOverview ( nextRange ) ;
@@ -363,7 +366,7 @@ export function useUsageDashboard() {
363366 }
364367 } ) ;
365368
366- const runBackgroundUpdateCheck = useEffectEvent ( async ( ) => {
369+ const performBackgroundUpdateCheck = useEffectEvent ( async ( ) => {
367370 let cachedInfo : UpdateCheckResponse | null = null ;
368371 try {
369372 const now = Date . now ( ) ;
@@ -473,6 +476,20 @@ export function useUsageDashboard() {
473476 }
474477 } ) ;
475478
479+ const runBackgroundUpdateCheck = useEffectEvent ( async ( ) => {
480+ if ( ! updateCheckInFlightRef . current ) {
481+ const checkPromise = performBackgroundUpdateCheck ( ) ;
482+ updateCheckInFlightRef . current = checkPromise ;
483+ void checkPromise . finally ( ( ) => {
484+ if ( updateCheckInFlightRef . current === checkPromise ) {
485+ updateCheckInFlightRef . current = null ;
486+ }
487+ } ) ;
488+ }
489+
490+ await updateCheckInFlightRef . current ;
491+ } ) ;
492+
476493 const bootstrap = useEffectEvent ( async ( ) => {
477494 if ( hasBootstrappedRef . current ) {
478495 return ;
@@ -532,6 +549,47 @@ export function useUsageDashboard() {
532549 void bootstrap ( ) ;
533550 } , [ bootstrap ] ) ;
534551
552+ useEffect ( ( ) => {
553+ if ( ! bootstrapped ) return ;
554+
555+ let cancelled = false ;
556+ let timer : number | null = null ;
557+
558+ const readTimestamp = ( key : string ) => {
559+ const value = Number ( localStorage . getItem ( key ) ) ;
560+ return Number . isFinite ( value ) && value > 0 ? value : null ;
561+ } ;
562+
563+ const scheduleNextCheck = ( ) => {
564+ const now = Date . now ( ) ;
565+ const lastSuccess = localStorage . getItem ( "last_update_check_result" )
566+ ? readTimestamp ( "last_update_check_time" )
567+ : null ;
568+ const lastFailure = readTimestamp ( "last_update_check_failed_time" ) ;
569+ const nextCheckAt = Math . max (
570+ lastSuccess ? lastSuccess + UPDATE_CHECK_INTERVAL_MS : now + UPDATE_CHECK_RETRY_MS ,
571+ lastFailure ? lastFailure + UPDATE_CHECK_RETRY_MS : now ,
572+ ) ;
573+
574+ timer = window . setTimeout ( ( ) => {
575+ void runBackgroundUpdateCheck ( ) . finally ( ( ) => {
576+ if ( ! cancelled ) {
577+ scheduleNextCheck ( ) ;
578+ }
579+ } ) ;
580+ } , Math . max ( 0 , nextCheckAt - now ) ) ;
581+ } ;
582+
583+ scheduleNextCheck ( ) ;
584+
585+ return ( ) => {
586+ cancelled = true ;
587+ if ( timer !== null ) {
588+ window . clearTimeout ( timer ) ;
589+ }
590+ } ;
591+ } , [ bootstrapped , runBackgroundUpdateCheck ] ) ;
592+
535593 // Re-fetch usage when the page/window regains focus after being inactive ≥5 min.
536594 useEffect ( ( ) => {
537595 if ( ! bootstrapped ) return ;
0 commit comments