@@ -27,7 +27,10 @@ define(function (require, exports, module) {
2727 require ( "./setup-login-service" ) ; // this adds loginService to KernalModeTrust
2828 require ( "./promotions" ) ;
2929
30+ const Metrics = require ( "utils/Metrics" ) ;
31+
3032 const MS_IN_DAY = 10 * 24 * 60 * 60 * 1000 ;
33+ const TEN_MINUTES = 10 * 60 * 1000 ;
3134
3235 const KernalModeTrust = window . KernalModeTrust ;
3336 if ( ! KernalModeTrust ) {
@@ -43,6 +46,25 @@ define(function (require, exports, module) {
4346 // Cached entitlements data
4447 let cachedEntitlements = null ;
4548
49+ // Last recorded state for entitlements monitoring
50+ let lastRecordedState = null ;
51+
52+ // Debounced trigger for entitlements changed
53+ let entitlementsChangedTimer = null ;
54+
55+ function _debounceEntitlementsChanged ( ) {
56+ if ( entitlementsChangedTimer ) {
57+ // already scheduled, skip
58+ return ;
59+ }
60+
61+ entitlementsChangedTimer = setTimeout ( ( ) => {
62+ LoginService . trigger ( EVENT_ENTITLEMENTS_CHANGED ) ;
63+ entitlementsChangedTimer = null ;
64+ } , 1000 ) ; // atmost 1 entitlement changed event will be triggered in a second
65+ }
66+
67+
4668 /**
4769 * Get entitlements from API or cache
4870 * Returns null if user is not logged in
@@ -96,7 +118,7 @@ define(function (require, exports, module) {
96118
97119 // Trigger event if entitlements changed
98120 if ( entitlementsChanged ) {
99- LoginService . trigger ( EVENT_ENTITLEMENTS_CHANGED , result ) ;
121+ _debounceEntitlementsChanged ( ) ;
100122 }
101123
102124 return cachedEntitlements ;
@@ -116,12 +138,139 @@ define(function (require, exports, module) {
116138 function clearEntitlements ( ) {
117139 if ( cachedEntitlements ) {
118140 cachedEntitlements = null ;
141+ _debounceEntitlementsChanged ( ) ;
142+ }
143+ }
144+
145+ /**
146+ * Check if any validTill time has expired
147+ */
148+ function validTillExpired ( entitlements , lastRecordedEntitlement ) {
149+ if ( ! entitlements ) {
150+ return null ;
151+ }
152+
153+ const now = Date . now ( ) ;
154+
155+ function isNewlyExpired ( validTill , lastValidTill ) {
156+ return (
157+ validTill &&
158+ validTill < now && // expired now
159+ ( ! lastValidTill || lastValidTill >= now ) // but wasn't expired before
160+ ) ;
161+ }
162+
163+ // Check plan validTill
164+ if ( entitlements . plan ) {
165+ const validTill = entitlements . plan . validTill ;
166+ const lastValidTill = ( lastRecordedEntitlement && lastRecordedEntitlement . plan )
167+ ? lastRecordedEntitlement . plan . validTill
168+ : null ;
169+
170+ if ( isNewlyExpired ( validTill , lastValidTill ) ) {
171+ return entitlements . plan . name || brackets . config . main_pro_plan ;
172+ }
173+ }
174+
175+ // Check entitlements validTill
176+ if ( entitlements . entitlements ) {
177+ for ( const key in entitlements . entitlements ) {
178+ const entitlement = entitlements . entitlements [ key ] ;
179+ if ( ! entitlement ) {
180+ continue ;
181+ }
182+
183+ const validTill = entitlement . validTill ;
184+ const lastValidTill = ( lastRecordedEntitlement &&
185+ lastRecordedEntitlement . entitlements &&
186+ lastRecordedEntitlement . entitlements [ key ] )
187+ ? lastRecordedEntitlement . entitlements [ key ] . validTill
188+ : null ;
189+
190+ if ( isNewlyExpired ( validTill , lastValidTill ) ) {
191+ return key ;
192+ }
193+ }
194+ }
195+
196+ return null ;
197+ }
198+
199+
200+ /**
201+ * Check if entitlements have changed from last recorded state
202+ */
203+ function haveEntitlementsChanged ( current , last ) {
204+ if ( ! last && ! current ) {
205+ return false ;
206+ }
207+ if ( ( ! last && current ) || ( ! current && last ) ) {
208+ return true ;
209+ }
210+ if ( ( ! last . entitlements && current . entitlements ) || ( current . entitlements && ! last . entitlements ) ) {
211+ return true ;
212+ }
213+
214+ // Check paidSubscriber changes
215+ const currentPaidSub = current . plan && current . plan . paidSubscriber ;
216+ const lastPaidSub = last . plan && last . plan . paidSubscriber ;
217+ if ( currentPaidSub !== lastPaidSub ) {
218+ return true ;
219+ }
220+
221+ // Check plan name changes
222+ const currentPlanName = current . plan && current . plan . name ;
223+ const lastPlanName = last . plan && last . plan . name ;
224+ if ( currentPlanName !== lastPlanName ) {
225+ return true ;
226+ }
119227
120- // Trigger event when entitlements are cleared
121- if ( LoginService . trigger ) {
122- LoginService . trigger ( EVENT_ENTITLEMENTS_CHANGED , null ) ;
228+ // Check entitlement activations
229+ if ( current . entitlements && last . entitlements ) {
230+ for ( const key of Object . keys ( current . entitlements ) ) {
231+ const currentActivated = current . entitlements [ key ] && current . entitlements [ key ] . activated ;
232+ const lastActivated = last . entitlements [ key ] && last . entitlements [ key ] . activated ;
233+ if ( currentActivated !== lastActivated ) {
234+ return true ;
235+ }
123236 }
124237 }
238+
239+ return false ;
240+ }
241+
242+ /**
243+ * Start the 10-minute interval timer for monitoring entitlements
244+ */
245+ function startEntitlementsMonitor ( ) {
246+ setInterval ( async ( ) => {
247+ try {
248+ const current = await getEffectiveEntitlements ( false ) ; // Get effective entitlements
249+
250+ // Check if we need to refresh
251+ const expiredPlanName = validTillExpired ( current , lastRecordedState ) ;
252+ const hasChanged = haveEntitlementsChanged ( current , lastRecordedState ) ;
253+
254+ if ( expiredPlanName || hasChanged ) {
255+ console . log ( `Entitlements monitor detected changes, Expired: ${ expiredPlanName } ,` +
256+ `changed: ${ hasChanged } refreshing...` ) ;
257+ Metrics . countEvent ( Metrics . EVENT_TYPE . PRO , "entRefresh" ,
258+ expiredPlanName ? "exp_" + expiredPlanName : "changed" ) ;
259+ await getEffectiveEntitlements ( true ) ; // Force refresh
260+ // if not logged in, the getEffectiveEntitlements will not trigger change even if some trial
261+ // entitlements changed. so we trigger a change anyway here. The debounce will take care of
262+ // multi fire and we are ok with multi fire 1 second apart.
263+ _debounceEntitlementsChanged ( ) ;
264+ }
265+
266+ // Update last recorded state
267+ lastRecordedState = current ;
268+ } catch ( error ) {
269+ console . error ( 'Entitlements monitor error:' , error ) ;
270+ }
271+ } , TEN_MINUTES ) ;
272+
273+ console . log ( 'Entitlements monitor started (10-minute interval)' ) ;
125274 }
126275
127276 /**
@@ -197,7 +346,8 @@ define(function (require, exports, module) {
197346 * @example
198347 * // Listen for entitlements changes
199348 * const LoginService = window.KernelModeTrust.loginService;
200- * LoginService.on(LoginService.EVENT_ENTITLEMENTS_CHANGED, (entitlements) => {
349+ * LoginService.on(LoginService.EVENT_ENTITLEMENTS_CHANGED, async() => {
350+ * const entitlements = await LoginService.getEffectiveEntitlements();
201351 * console.log('Entitlements changed:', entitlements);
202352 * // Update UI based on new entitlements
203353 * });
@@ -278,4 +428,7 @@ define(function (require, exports, module) {
278428 LoginService . getEffectiveEntitlements = getEffectiveEntitlements ;
279429 LoginService . clearEntitlements = clearEntitlements ;
280430 LoginService . EVENT_ENTITLEMENTS_CHANGED = EVENT_ENTITLEMENTS_CHANGED ;
431+
432+ // Start the entitlements monitor timer
433+ startEntitlementsMonitor ( ) ;
281434} ) ;
0 commit comments