@ -206,9 +206,146 @@ export default class DailyNotificationSection extends Vue {
/ * *
* Initialize component state on mount
* Checks platform support and syncs with plugin state
*
* * * Token Refresh on Mount : * *
* - Refreshes native fetcher configuration to ensure plugin has valid token
* - This handles cases where app was closed for extended periods
* - Token refresh happens automatically without user interaction
*
* * * App Resume Listener : * *
* - Listens for Capacitor 'resume' event to refresh token when app comes to foreground
* - Ensures plugin always has fresh token for background prefetch operations
* - Cleaned up in ` beforeDestroy() ` lifecycle hook
* /
async mounted ( ) : Promise < void > {
await this . initializeState ( ) ;
/ / R e f r e s h n a t i v e f e t c h e r c o n f i g u r a t i o n o n m o u n t
/ / T h i s e n s u r e s p l u g i n h a s v a l i d t o k e n e v e n i f a p p w a s c l o s e d f o r e x t e n d e d p e r i o d s
await this . refreshNativeFetcherConfig ( ) ;
/ / L i s t e n f o r a p p r e s u m e e v e n t s t o r e f r e s h t o k e n w h e n a p p c o m e s t o f o r e g r o u n d
/ / T h i s i s p a r t o f t h e p r o a c t i v e t o k e n r e f r e s h s t r a t e g y
document . addEventListener ( "resume" , this . handleAppResume ) ;
}
/ * *
* Cleanup on component destroy
* /
beforeDestroy ( ) : void {
document . removeEventListener ( "resume" , this . handleAppResume ) ;
}
/ * *
* Handle app resume event - refresh native fetcher configuration
*
* This method is called when the app comes to foreground ( via Capacitor 'resume' event ) .
* It proactively refreshes the JWT token to ensure the plugin has valid authentication
* for background prefetch operations .
*
* * * Why refresh on resume ? * *
* - Tokens expire after 72 hours
* - App may have been closed for extended periods
* - Refreshing ensures plugin has valid token for next prefetch cycle
* - No user interaction required - happens automatically
*
* @ see { @ link refreshNativeFetcherConfig } For implementation details
* /
async handleAppResume ( ) : Promise < void > {
logger . debug (
"[DailyNotificationSection] App resumed, refreshing native fetcher config" ,
) ;
await this . refreshNativeFetcherConfig ( ) ;
}
/ * *
* Refresh native fetcher configuration with fresh JWT token
*
* This method ensures the daily notification plugin has a valid authentication token
* for background prefetch operations . It ' s called proactively to prevent token expiration
* issues during offline periods .
*
* * * Refresh Triggers : * *
* - Component mount ( when notification settings page loads )
* - App resume ( when app comes to foreground )
* - Notification enabled ( when user enables daily notifications )
*
* * * Token Refresh Strategy ( Hybrid Approach ) : * *
* - Tokens are valid for 72 hours ( see ` accessTokenForBackground ` )
* - Tokens are refreshed proactively when app is already open
* - If token expires while offline , plugin uses cached content
* - Next time app opens , token is automatically refreshed
*
* * * Why This Approach ? * *
* - No app wake - up required ( tokens refresh when app is already open )
* - Works offline ( 72 - hour validity supports extended offline periods )
* - Automatic ( no user interaction required )
* - Includes starred plans ( fetcher receives user ' s starred plans for prefetch )
* - Graceful degradation ( if refresh fails , cached content still works )
*
* * * Error Handling : * *
* - Errors are logged but not shown to user ( background operation )
* - Returns early if notifications not supported or disabled
* - Returns early if API server not configured
* - Failures don ' t interrupt user experience
*
* @ returns Promise that resolves when refresh completes ( or fails silently )
*
* @ example
* ` ` ` typescript
* / / C a l l e d a u t o m a t i c a l l y o n m o u n t / r e s u m e
* await this . refreshNativeFetcherConfig ( ) ;
* ` ` `
*
* @ see { @ link CapacitorPlatformService . configureNativeFetcher } For token generation
* @ see { @ link accessTokenForBackground } For 72 - hour token generation
* /
async refreshNativeFetcherConfig ( ) : Promise < void > {
try {
const platformService = PlatformServiceFactory . getInstance ( ) ;
/ / E a r l y r e t u r n : O n l y r e f r e s h i f n o t i f i c a t i o n s a r e s u p p o r t e d a n d e n a b l e d
/ / T h i s p r e v e n t s u n n e c e s s a r y w o r k w h e n n o t i f i c a t i o n s a r e n ' t b e i n g u s e d
if ( ! this . notificationsSupported || ! this . nativeNotificationEnabled ) {
return ;
}
/ / G e t s e t t i n g s f o r A P I s e r v e r a n d s t a r r e d p l a n s
/ / A P I s e r v e r t e l l s p l u g i n w h e r e t o f e t c h c o n t e n t f r o m
/ / S t a r r e d p l a n s t e l l p l u g i n w h i c h p l a n s t o p r e f e t c h
const settings = await this . $accountSettings ( ) ;
const apiServer = settings . apiServer || "" ;
if ( ! apiServer ) {
logger . warn (
"[DailyNotificationSection] No API server configured, skipping native fetcher refresh" ,
) ;
return ;
}
/ / G e t s t a r r e d p l a n s f r o m s e t t i n g s
/ / T h e s e a r e p a s s e d t o t h e p l u g i n s o i t k n o w s w h i c h p l a n s t o p r e f e t c h
const starredPlanHandleIds = settings . starredPlanHandleIds || [ ] ;
/ / C o n f i g u r e n a t i v e f e t c h e r w i t h f r e s h t o k e n
/ / T h e j w t p a r a m e t e r i s i g n o r e d - c o n f i g u r e N a t i v e F e t c h e r g e n e r a t e s i t a u t o m a t i c a l l y
/ / T h i s e n s u r e s w e a l w a y s h a v e a f r e s h t o k e n w i t h c u r r e n t e x p i r a t i o n t i m e
await platformService . configureNativeFetcher ( {
apiServer ,
jwt : "" , / / W i l l b e g e n e r a t e d a u t o m a t i c a l l y b y c o n f i g u r e N a t i v e F e t c h e r
starredPlanHandleIds ,
} ) ;
logger . info (
"[DailyNotificationSection] Native fetcher configuration refreshed" ,
) ;
} catch ( error ) {
/ / D o n ' t s h o w e r r o r t o u s e r - t h i s i s a b a c k g r o u n d o p e r a t i o n
/ / F a i l u r e s a r e l o g g e d f o r d e b u g g i n g b u t d o n ' t i n t e r r u p t u s e r e x p e r i e n c e
/ / I f r e f r e s h f a i l s , p l u g i n w i l l u s e e x i s t i n g t o k e n ( i f s t i l l v a l i d ) o r c a c h e d c o n t e n t
logger . error (
"[DailyNotificationSection] Failed to refresh native fetcher config:" ,
error ,
) ;
}
}
/ * *
@ -235,8 +372,7 @@ export default class DailyNotificationSection extends Vue {
this . notificationsSupported = true ;
this . notificationStatus = status ;
/ / C R I T I C A L : S y n c w i t h p l u g i n s t a t e f i r s t ( s o u r c e o f t r u t h )
/ / P l u g i n m a y h a v e a n e x i s t i n g s c h e d u l e e v e n i f s e t t i n g s d o n ' t
/ / P l u g i n s t a t e i s t h e s o u r c e o f t r u t h
if ( status . isScheduled && status . scheduledTime ) {
/ / P l u g i n h a s a s c h e d u l e d n o t i f i c a t i o n - s y n c U I t o m a t c h
this . nativeNotificationEnabled = true ;
@ -244,31 +380,11 @@ export default class DailyNotificationSection extends Vue {
this . nativeNotificationTime = formatTimeForDisplay (
status . scheduledTime ,
) ;
/ / A l s o s y n c s e t t i n g s t o m a t c h p l u g i n s t a t e
const settings = await this . $accountSettings ( ) ;
if ( settings . nativeNotificationTime !== status . scheduledTime ) {
await this . $saveSettings ( {
nativeNotificationTime : status . scheduledTime ,
nativeNotificationTitle :
settings . nativeNotificationTitle || this . nativeNotificationTitle ,
nativeNotificationMessage :
settings . nativeNotificationMessage ||
this . nativeNotificationMessage ,
} ) ;
}
} else {
/ / N o p l u g i n s c h e d u l e - c h e c k s e t t i n g s f o r u s e r p r e f e r e n c e
const settings = await this . $accountSettings ( ) ;
const nativeNotificationTime = settings . nativeNotificationTime || "" ;
this . nativeNotificationEnabled = ! ! nativeNotificationTime ;
this . nativeNotificationTimeStorage = nativeNotificationTime ;
if ( nativeNotificationTime ) {
this . nativeNotificationTime = formatTimeForDisplay (
nativeNotificationTime ,
) ;
}
/ / N o p l u g i n s c h e d u l e - U I d e f a u l t s t o d i s a b l e d
this . nativeNotificationEnabled = false ;
this . nativeNotificationTimeStorage = "" ;
this . nativeNotificationTime = "" ;
}
} catch ( error ) {
logger . error ( "[DailyNotificationSection] Failed to initialize:" , error ) ;
@ -452,16 +568,14 @@ export default class DailyNotificationSection extends Vue {
priority : "high" ,
} ) ;
/ / S a v e t o s e t t i n g s
await this . $saveSettings ( {
nativeNotificationTime : this . nativeNotificationTimeStorage ,
nativeNotificationTitle : this . nativeNotificationTitle ,
nativeNotificationMessage : this . nativeNotificationMessage ,
} ) ;
/ / U p d a t e U I s t a t e
this . nativeNotificationEnabled = true ;
/ / R e f r e s h n a t i v e f e t c h e r c o n f i g u r a t i o n w i t h f r e s h t o k e n
/ / T h i s e n s u r e s p l u g i n h a s v a l i d a u t h e n t i c a t i o n w h e n n o t i f i c a t i o n s a r e f i r s t e n a b l e d
/ / T o k e n w i l l b e v a l i d f o r 7 2 h o u r s , s u p p o r t i n g o f f l i n e p r e f e t c h o p e r a t i o n s
await this . refreshNativeFetcherConfig ( ) ;
this . notify . success (
"Daily notification scheduled successfully" ,
TIMEOUTS . SHORT ,
@ -492,13 +606,6 @@ export default class DailyNotificationSection extends Vue {
/ / C a n c e l n o t i f i c a t i o n v i a P l a t f o r m S e r v i c e
await platformService . cancelDailyNotification ( ) ;
/ / C l e a r s e t t i n g s
await this . $saveSettings ( {
nativeNotificationTime : "" ,
nativeNotificationTitle : "" ,
nativeNotificationMessage : "" ,
} ) ;
/ / U p d a t e U I s t a t e
this . nativeNotificationEnabled = false ;
this . nativeNotificationTime = "" ;
@ -558,10 +665,7 @@ export default class DailyNotificationSection extends Vue {
if ( this . nativeNotificationEnabled ) {
await this . updateNotificationTime ( this . nativeNotificationTimeStorage ) ;
} else {
/ / J u s t s a v e t h e t i m e p r e f e r e n c e
await this . $saveSettings ( {
nativeNotificationTime : this . nativeNotificationTimeStorage ,
} ) ;
/ / J u s t u p d a t e l o c a l s t a t e ( t i m e p r e f e r e n c e s t o r e d i n c o m p o n e n t )
this . showTimeEdit = false ;
this . notify . success ( "Notification time saved" , TIMEOUTS . SHORT ) ;
}
@ -574,12 +678,9 @@ export default class DailyNotificationSection extends Vue {
async updateNotificationTime ( newTime : string ) : Promise < void > {
/ / n e w T i m e i s i n " H H : m m " f o r m a t f r o m H T M L 5 t i m e i n p u t
if ( ! this . nativeNotificationEnabled ) {
/ / I f n o t i f i c a t i o n i s d i s a b l e d , j u s t s a v e t h e t i m e p r e f e r e n c e
/ / I f n o t i f i c a t i o n i s d i s a b l e d , j u s t u p d a t e l o c a l s t a t e
this . nativeNotificationTimeStorage = newTime ;
this . nativeNotificationTime = formatTimeForDisplay ( newTime ) ;
await this . $saveSettings ( {
nativeNotificationTime : newTime ,
} ) ;
this . showTimeEdit = false ;
return ;
}
@ -605,11 +706,6 @@ export default class DailyNotificationSection extends Vue {
this . nativeNotificationTimeStorage = newTime ;
this . nativeNotificationTime = formatTimeForDisplay ( newTime ) ;
/ / 4 . S a v e t o s e t t i n g s
await this . $saveSettings ( {
nativeNotificationTime : newTime ,
} ) ;
this . notify . success (
"Notification time updated successfully" ,
TIMEOUTS . SHORT ,