Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1690,5 +1690,5 @@ define({
"PROMO_PRO_UNLOCK_MESSAGE": "Subscribe now to unlock these advanced features:",
"PROMO_PRO_TRIAL_DAYS_LEFT": "Phoenix Pro Trial ({0} days left)",
"GET_PHOENIX_PRO": "Get Phoenix Pro",
"USER_FREE_PLAN_NAME": "Free Plan"
"USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE": "Community Edition"
});
2 changes: 1 addition & 1 deletion src/services/entitlements.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ define(function (require, exports, module) {
const currentDate = Date.now();
return {
paidSubscriber: false,
name: Strings.USER_FREE_PLAN_NAME,
name: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE,
validTill: currentDate + (FREE_PLAN_VALIDITY_DAYS * MS_IN_DAY)
};
}
Expand Down
47 changes: 38 additions & 9 deletions src/services/login-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ define(function (require, exports, module) {
const accountBaseURL = LoginService.getAccountBaseURL();
const language = brackets.getLocale();
const currentVersion = window.AppConfig.apiVersion || "1.0.0";
let url = `${accountBaseURL}/getAppEntitlements?lang=${language}&version=${currentVersion}`;
let url = `${accountBaseURL}/getAppEntitlements?lang=${language}&version=${currentVersion}`+
`&platform=${Phoenix.platform}&appType=${Phoenix.isNativeApp ? "desktop" : "browser"}}`;
let fetchOptions = {
method: 'GET',
headers: {
Expand Down Expand Up @@ -352,29 +353,50 @@ define(function (require, exports, module) {
/**
* Start the 10-minute interval timer for monitoring entitlements
*/
function startEntitlementsMonitor() {
function startEffectiveEntitlementsMonitor() {
// Reconcile effective entitlements from server. So the effective entitlements api injects trial
// entitlements data. but only the server fetch will trigger the entitlements change event.
// so in here, we observe the effective entitlements, and if the effective entitlements are changed,
// since the last triggered state, we trigger a change event. This only concerens with the effective
// entitlement changes. This will not logout the user if user logged out from the server admin panel,
// but his entitlements will be cleared by this call anyways.

// At app start we refresh entitlements, then only one each user action like user clicks on profile icon,
// or if some user hits some backend api, we will refresh entitlements. But here, we periodically refresh
// entitlements from the server every 10 minutes, but only trigger entitlement change events only if some
// effective entitlement(Eg. trial) data changed or any validity expired.
if(Phoenix.isTestWindow){
return;
}
setTimeout( async function() {
// prime the entitlement monitor with the current effective entitlements, after app start, the system would
// have resolved any existing login info by now and effective entitlements would be available if any.
lastRecordedState = await getEffectiveEntitlements(false);
}, 30000);
setInterval(async () => {
try {
const current = await getEffectiveEntitlements(false); // Get effective entitlements
// Get fresh effective entitlements
const freshEntitlements = await getEffectiveEntitlements(true);

// Check if we need to refresh
const expiredPlanName = KernalModeTrust.LoginUtils.validTillExpired(current, lastRecordedState);
const hasChanged = KernalModeTrust.LoginUtils.haveEntitlementsChanged(current, lastRecordedState);
const expiredPlanName = KernalModeTrust.LoginUtils
.validTillExpired(freshEntitlements, lastRecordedState);
const hasChanged = KernalModeTrust.LoginUtils
.haveEntitlementsChanged(freshEntitlements, lastRecordedState);

if (expiredPlanName || hasChanged) {
console.log(`Entitlements monitor detected changes, Expired: ${expiredPlanName},` +
`changed: ${hasChanged} refreshing...`);
Metrics.countEvent(Metrics.EVENT_TYPE.PRO, "entRefresh",
expiredPlanName ? "exp_"+expiredPlanName : "changed");
await getEffectiveEntitlements(true); // Force refresh
// if not logged in, the getEffectiveEntitlements will not trigger change even if some trial
// entitlements changed. so we trigger a change anyway here. The debounce will take care of
// multi fire and we are ok with multi fire 1 second apart.
_debounceEntitlementsChanged();
}

// Update last recorded state
lastRecordedState = current;
lastRecordedState = freshEntitlements;
} catch (error) {
console.error('Entitlements monitor error:', error);
}
Expand All @@ -394,7 +416,8 @@ define(function (require, exports, module) {
entitlements.plan = {
...entitlements.plan,
paidSubscriber: false,
name: Strings.USER_FREE_PLAN_NAME,
name: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE,
fullName: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE,
validTill: currentDate + (FREE_PLAN_VALIDITY_DAYS * MS_IN_DAY)
};
}
Expand Down Expand Up @@ -431,6 +454,8 @@ define(function (require, exports, module) {
* plan: {
* paidSubscriber: true, // Always true for trial users
* name: "Phoenix Pro"
* fullName: "Phoenix Pro" // this can be deceptive name like "Phoenix Pro For Education" to use in
* // profile popup, not main branding
* },
* isInProTrial: true, // Indicates this is a trial user
* trialDaysRemaining: number, // Days left in trial
Expand All @@ -456,6 +481,8 @@ define(function (require, exports, module) {
* lang: string,
* plan: {
* name: "Phoenix Pro",
* fullName: "Phoenix Pro" // this can be deceptive name like "Phoenix Pro For Education" to use in
* // profile popup, not main branding
* paidSubscriber: boolean,
* validTill: number // Timestamp
* },
Expand Down Expand Up @@ -531,6 +558,7 @@ define(function (require, exports, module) {
...serverEntitlements.plan,
paidSubscriber: true,
name: brackets.config.main_pro_plan,
fullName: brackets.config.main_pro_plan,
validTill: dateNowFn() + trialDaysRemaining * MS_IN_DAY
},
isInProTrial: true,
Expand All @@ -552,6 +580,7 @@ define(function (require, exports, module) {
plan: {
paidSubscriber: true,
name: brackets.config.main_pro_plan,
fullName: brackets.config.main_pro_plan,
validTill: dateNowFn() + trialDaysRemaining * MS_IN_DAY
},
isInProTrial: true,
Expand Down Expand Up @@ -597,7 +626,7 @@ define(function (require, exports, module) {
}

// Start the entitlements monitor timer
startEntitlementsMonitor();
startEffectiveEntitlementsMonitor();

exports.init = init;
// no public exports to prevent extension tampering
Expand Down
12 changes: 7 additions & 5 deletions src/services/profile-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,19 +217,21 @@ define(function (require, exports, module) {
// Use kernal mode apis for trusted check of pro features.
Phoenix.pro.plan = {
paidSubscriber: false,
name: "Community Edition"
name: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE,
fullName: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE
};
}

if (entitlements && entitlements.plan){
Phoenix.pro.plan = {
paidSubscriber: entitlements.plan.paidSubscriber,
name: entitlements.plan.name,
fullName: entitlements.plan.fullName,
validTill: entitlements.plan.validTill
};
}
if (entitlements && entitlements.plan && entitlements.plan.paidSubscriber) {
// Pro user (paid subscriber or trial): show plan name with feather icon
// Pro user (paid subscriber or trial): show short name branding with `name feather icon`(not full name)
let displayName = entitlements.plan.name || brackets.config.main_pro_plan;
if (entitlements.isInProTrial) {
displayName = brackets.config.main_pro_plan; // Just "Phoenix Pro" for branding, not "Phoenix Pro Trial"
Expand Down Expand Up @@ -379,15 +381,15 @@ define(function (require, exports, module) {
} else {
// For paid users: regular plan name with icon
const proTitle = `<span class="phoenix-pro-title">
<span class="pro-plan-name user-plan-name">${entitlements.plan.name}</span>
<span class="pro-plan-name user-plan-name">${entitlements.plan.fullName}</span>
<i class="fa-solid fa-feather" style="margin-left: 3px;"></i>
</span>`;
$planName.addClass('user-plan-paid').html(proTitle);
$getProLink.addClass('forced-hidden');
}
} else {
// Use simple text for free users
$planName.addClass('user-plan-free').text(entitlements.plan.name);
$planName.addClass('user-plan-free').text(entitlements.plan.fullName);
}
} else {
$getProLink.removeClass('forced-hidden');
Expand Down Expand Up @@ -436,7 +438,7 @@ define(function (require, exports, module) {
initials: profileData.profileIcon.initials,
avatarColor: profileData.profileIcon.color,
planClass: "user-plan-free",
planName: Strings.USER_FREE_PLAN_NAME,
planName: Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE,
titleText: "Ai Quota Used",
usageText: "100 / 200 credits",
usedPercent: 0,
Expand Down
4 changes: 3 additions & 1 deletion test/spec/login-browser-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ define(function (require, exports, module) {
entitlementsResponse.plan = {
paidSubscriber: true,
name: "Phoenix Pro",
fullName: "Phoenix Pro",
validTill: validTill
};
entitlementsResponse.entitlements = {
Expand All @@ -196,7 +197,8 @@ define(function (require, exports, module) {
} else {
entitlementsResponse.plan = {
paidSubscriber: false,
name: "Free Plan"
name: "Free Plan",
fullName: "Free Plan"
};
}

Expand Down
4 changes: 3 additions & 1 deletion test/spec/login-desktop-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ define(function (require, exports, module) {
entitlementsResponse.plan = {
paidSubscriber: true,
name: "Phoenix Pro",
fullName: "Phoenix Pro",
validTill: validTill
};
entitlementsResponse.entitlements = {
Expand All @@ -218,7 +219,8 @@ define(function (require, exports, module) {
} else {
entitlementsResponse.plan = {
paidSubscriber: false,
name: "Free Plan"
name: "Free Plan",
fullName: "Free Plan"
};
}

Expand Down
6 changes: 3 additions & 3 deletions test/spec/login-shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ define(function (require, exports, module) {
await verifyProBranding(false, "no pro branding to start with");

// Verify entitlements API consistency for logged out user with expired trial
await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME },
await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE },
"free plan for logged out user with expired trial");
await verifyIsInProTrialEntitlement(false, "no trial for user with expired trial");
await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining for expired trial");
Expand All @@ -505,7 +505,7 @@ define(function (require, exports, module) {
await verifyProBranding(false, "after trial free user login");

// Verify entitlements API consistency for logged in free user
await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME },
await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE },
"free plan for logged in user with expired trial");
await verifyIsInProTrialEntitlement(false, "still no trial after login");
await verifyLiveEditEntitlement({ activated: false }, "live edit still deactivated after login");
Expand Down Expand Up @@ -551,7 +551,7 @@ define(function (require, exports, module) {
await verifyProBranding(false, "no pro branding initially due to expired entitlements");

// Verify entitlements API consistency for logged out user with no trial
await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME },
await verifyPlanEntitlements({ paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE },
"free plan for logged out user with no trial");
await verifyIsInProTrialEntitlement(false, "no trial for logged out user");
await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining");
Expand Down
2 changes: 1 addition & 1 deletion test/spec/login-utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,4 +497,4 @@ define(function (require, exports, module) {
});
});
});
});
});
4 changes: 2 additions & 2 deletions test/spec/promotions-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ define(function (require, exports, module) {
LoginServiceExports._validateAndFilterEntitlements(expiredPlanEntitlements);

expect(expiredPlanEntitlements.plan.paidSubscriber).toBe(false);
expect(expiredPlanEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME);
expect(expiredPlanEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE);
expect(expiredPlanEntitlements.plan.validTill).toBeGreaterThan(mockNow);

// Test valid plan remains unchanged
Expand Down Expand Up @@ -848,7 +848,7 @@ define(function (require, exports, module) {
LoginServiceExports._validateAndFilterEntitlements(noValidTillEntitlements);

expect(noValidTillEntitlements.plan.paidSubscriber).toBe(false);
expect(noValidTillEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME);
expect(noValidTillEntitlements.plan.name).toBe(testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE);
});

it("should validate and filter expired feature entitlements", function () {
Expand Down
Loading