1+ import frontend , { PageState } from '@/store/frontend'
2+
3+ // Delays are multiplied by these factors when the page is not actively focused,
4+ // reducing unnecessary network traffic and CPU usage for background tabs.
5+ const PAGE_STATE_MULTIPLIERS : Record < PageState , number > = { focused : 1 , blurred : 5 , hidden : 10 }
6+
7+ // When the page regains focus, we need to explicitly notify all instances so they can
8+ // cancel their throttled (long) timeouts and fire immediately with fresh data.
9+ // This can't rely on Vuex reactivity alone because setTimeout callbacks aren't reactive —
10+ // a sleeping timeout won't wake up just because a store value changed.
11+ const pageResumeListeners = new Set < ( ) => void > ( )
12+ if ( typeof document !== 'undefined' ) {
13+ const notify = ( ) => pageResumeListeners . forEach ( ( fn ) => fn ( ) )
14+ document . addEventListener ( 'visibilitychange' , ( ) => { if ( ! document . hidden ) notify ( ) } )
15+ window . addEventListener ( 'focus' , notify )
16+ }
17+
118/**
219 * Represents a function that can be OneMoreTime valid action
320 */
@@ -39,6 +56,8 @@ export interface OneMoreTimeOptions {
3956 * OneMoreTime instance.
4057 */
4158 disposeWith ?: unknown
59+
60+ disablePageThrottle ?: boolean
4261}
4362
4463/**
@@ -55,6 +74,12 @@ export class OneMoreTime {
5574
5675 private timeoutId ?: ReturnType < typeof setTimeout >
5776
77+ private onPageResume = ( ) => {
78+ if ( this . isDisposed || this . isPaused || this . isRunning || ! this . timeoutId ) return
79+ this . killTask ( )
80+ this . start ( )
81+ }
82+
5883 /**
5984 * Constructs an instance of OneMoreTime, optionally starting the action immediately.
6085 * @param {OneMoreTimeOptions } options Configuration options for the instance.
@@ -65,10 +90,17 @@ export class OneMoreTime {
6590 private action ?: OneMoreTimeAction ,
6691 ) {
6792 this . watchDisposeWith ( )
93+ if ( ! this . options . disablePageThrottle ) pageResumeListeners . add ( this . onPageResume )
6894 // One more time
6995 this . softStart ( )
7096 }
7197
98+ private getEffectiveDelay ( baseDelay ?: number ) : number | undefined {
99+ if ( baseDelay === undefined ) return undefined
100+ if ( this . options . disablePageThrottle ) return baseDelay
101+ return baseDelay * PAGE_STATE_MULTIPLIERS [ frontend . page_state ]
102+ }
103+
72104 private killTask ( ) : void {
73105 if ( this . timeoutId ) {
74106 clearTimeout ( this . timeoutId )
@@ -85,6 +117,7 @@ export class OneMoreTime {
85117 // eslint-disable-next-line
86118 if ( ! ref . deref ( ) || ref . deref ( ) . _isDestroyed ) {
87119 this . isDisposed = true
120+ pageResumeListeners . delete ( this . onPageResume )
88121 this . killTask ( )
89122 clearInterval ( id )
90123 }
@@ -95,6 +128,7 @@ export class OneMoreTime {
95128 // Celebrate and dance so free
96129 [ Symbol . dispose ] ( ) : void {
97130 this . isDisposed = true
131+ pageResumeListeners . delete ( this . onPageResume )
98132 this . killTask ( )
99133 }
100134
@@ -150,13 +184,13 @@ export class OneMoreTime {
150184 this . options . onError ?.( error )
151185 // Oh yeah, alright, don't stop the dancing
152186 // eslint-disable-next-line no-promise-executor-return
153- await new Promise ( ( resolve ) => setTimeout ( resolve , this . options . errorDelay ) )
187+ await new Promise ( ( resolve ) => setTimeout ( resolve , this . getEffectiveDelay ( this . options . errorDelay ) ) )
154188 } finally {
155189 this . isRunning = false
156190 }
157191
158192 if ( ! this . isPaused && ! this . isDisposed ) {
159- this . timeoutId = setTimeout ( ( ) => this . start ( ) , this . options . delay )
193+ this . timeoutId = setTimeout ( ( ) => this . start ( ) , this . getEffectiveDelay ( this . options . delay ) )
160194 }
161195 }
162196
0 commit comments