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
3 changes: 3 additions & 0 deletions src/services/login-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ define(function (require, exports, module) {
Metrics.countEvent(Metrics.EVENT_TYPE.AUTH, "browserLogin", "browser");
}, 1500);
}
// on login we fire an entitlements changed event forcefully as new user came online
// even if entitlements didn't change(entitlements may not change between trial users for eg.).
LoginService._debounceEntitlementsChanged();
}

function _cancelLoginWaiting() {
Expand Down
5 changes: 4 additions & 1 deletion src/services/login-desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,16 @@ define(function (require, exports, module) {
if(resolveResponse.userDetails) {
// the user has validated the creds
userProfile = resolveResponse.userDetails;
isLoggedInUser = true;
ProfileMenu.setLoggedIn(userProfile.profileIcon.initials, userProfile.profileIcon.color);
await KernalModeTrust.setCredential(KernalModeTrust.CRED_KEY_API, JSON.stringify(userProfile));
// bump the version so that in multi windows, the other window gets notified of the change
PreferencesManager.stateManager.set(PREF_USER_PROFILE_VERSION, crypto.randomUUID());
checkAgain = false;
isLoggedInUser = true;
dialog.close();
// on login we fire an entitlements changed event forcefully as new user came online
// even if entitlements didn't change(entitlements may not change between trial users for eg.).
LoginService._debounceEntitlementsChanged();
}
} catch (e) {
console.error("Failed to check login status.", e);
Expand Down
2 changes: 2 additions & 0 deletions src/services/login-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ define(function (require, exports, module) {
}
// Reset device license state so it's re-evaluated on next entitlement check
deviceLicensePrimed = false;
await _clearCachedEntitlements();
}


Expand Down Expand Up @@ -708,6 +709,7 @@ define(function (require, exports, module) {
LoginService.isLicensedDevice = isLicensedDevice;
LoginService.isLicensedDeviceSystemWide = isLicensedDeviceSystemWide;
LoginService.getDeviceID = getDeviceID;
LoginService._debounceEntitlementsChanged = _debounceEntitlementsChanged;
LoginService.EVENT_ENTITLEMENTS_CHANGED = EVENT_ENTITLEMENTS_CHANGED;

async function handleReinstallCreds() {
Expand Down
45 changes: 34 additions & 11 deletions src/utils/Metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,20 @@
* @module utils/Metrics
*/
define(function (require, exports, module) {
const KernalModeTrust = window.KernalModeTrust;
if(!KernalModeTrust){
// integrated extensions will have access to kernal mode, but not external extensions
throw new Error("Metrics should have access to KernalModeTrust. Cannot boot without trust ring");
}

const MAX_AUDIT_ENTRIES = 3000,
ONE_DAY = 24 * 60* 60 * 1000;
let initDone = false,
disabled = false,
loggedDataForAudit = new Map();

let isFirstUseDay;
let userID, isPowerUserFn;
let userID, isPowerUserFn, powerUserPrefix;
let cachedIsPowerUser = false;

function _setUserID() {
Expand Down Expand Up @@ -260,6 +266,25 @@ define(function (require, exports, module) {
document.getElementsByTagName('head')[0].appendChild(script);
}

async function _setPowerUserPrefix() {
powerUserPrefix = null;
const EntitlementsManager = KernalModeTrust.EntitlementsManager;
if(cachedIsPowerUser){
// A power user is someone who used Phoenix at least 3 days/8 hours in the last two weeks
powerUserPrefix = "P";
} else if(!isFirstUseDay){
// A repeat user is a user who has used phoenix at least one other day before
powerUserPrefix = "R";
}
if(EntitlementsManager.isLoggedIn()){
if(await EntitlementsManager.isPaidSubscriber()){
powerUserPrefix = "S"; // subscriber
return;
}
powerUserPrefix = "L"; // logged in user
}
}

/**
* We are transitioning to our own analytics instead of google as we breached the free user threshold of google
* and paid plans for GA starts at 100,000 USD.
Expand All @@ -275,10 +300,14 @@ define(function (require, exports, module) {
if (initOptions.isPowerUserFn) {
isPowerUserFn = initOptions.isPowerUserFn;
cachedIsPowerUser = isPowerUserFn(); // only call once to avoid heavy computations repeatedly
_setPowerUserPrefix();
setInterval(()=>{
cachedIsPowerUser = isPowerUserFn();
_setPowerUserPrefix();
}, ONE_DAY);
}
KernalModeTrust.EntitlementsManager.on(KernalModeTrust.EntitlementsManager.EVENT_ENTITLEMENTS_CHANGED,
_setPowerUserPrefix);
}

// some events generate too many ga events that ga can't handle. ignore them.
Expand Down Expand Up @@ -363,12 +392,9 @@ define(function (require, exports, module) {
* @type {function}
*/
function countEvent(eventType, eventCategory, eventSubCategory, count= 1) {
if(cachedIsPowerUser){
if(powerUserPrefix){
// emit power user metrics too
_countEvent(`P-${eventType}`, eventCategory, eventSubCategory, count);
} else if(!isFirstUseDay){
// emit repeat user metrics too
_countEvent(`R-${eventType}`, eventCategory, eventSubCategory, count);
_countEvent(`${powerUserPrefix}-${eventType}`, eventCategory, eventSubCategory, count);
}
_countEvent(eventType, eventCategory, eventSubCategory, count);
}
Expand All @@ -394,12 +420,9 @@ define(function (require, exports, module) {
* @type {function}
*/
function valueEvent(eventType, eventCategory, eventSubCategory, value) {
if(cachedIsPowerUser){
if(powerUserPrefix){
// emit power user metrics too
_valueEvent(`P-${eventType}`, eventCategory, eventSubCategory, value);
} else if(!isFirstUseDay){
// emit repeat user metrics too
_valueEvent(`R-${eventType}`, eventCategory, eventSubCategory, value);
_valueEvent(`${powerUserPrefix}-${eventType}`, eventCategory, eventSubCategory, value);
}
_valueEvent(eventType, eventCategory, eventSubCategory, value);
}
Expand Down
6 changes: 6 additions & 0 deletions test/spec/login-browser-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ define(function (require, exports, module) {
LoginBrowserExports,
ProDialogsExports,
EntitlementsExports,
entitlementsService,
originalOpen,
originalFetch;

Expand Down Expand Up @@ -77,6 +78,9 @@ define(function (require, exports, module) {
LoginBrowserExports = testWindow._test_login_browser_exports;
ProDialogsExports = testWindow._test_pro_dlg_login_exports;
EntitlementsExports = testWindow._test_entitlements_exports;
entitlementsService = EntitlementsExports.EntitlementsService;
entitlementsService.on(entitlementsService.EVENT_ENTITLEMENTS_CHANGED,
LoginShared.entitlmentsChangedHandler);

// Store original functions for restoration
originalOpen = testWindow.open;
Expand Down Expand Up @@ -109,6 +113,8 @@ define(function (require, exports, module) {

afterAll(async function () {
// Restore original functions
entitlementsService.off(entitlementsService.EVENT_ENTITLEMENTS_CHANGED,
LoginShared.entitlmentsChangedHandler);
testWindow.open = originalOpen;

// Restore all fetch function overrides
Expand Down
6 changes: 6 additions & 0 deletions test/spec/login-desktop-integ-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ define(function (require, exports, module) {
LoginDesktopExports,
ProDialogsExports,
EntitlementsExports,
entitlementsService,
originalOpenURLInDefaultBrowser,
originalCopyToClipboard,
originalFetch;
Expand Down Expand Up @@ -84,6 +85,9 @@ define(function (require, exports, module) {
LoginDesktopExports = testWindow._test_login_desktop_exports;
ProDialogsExports = testWindow._test_pro_dlg_login_exports;
EntitlementsExports = testWindow._test_entitlements_exports;
entitlementsService = EntitlementsExports.EntitlementsService;
entitlementsService.on(entitlementsService.EVENT_ENTITLEMENTS_CHANGED,
LoginShared.entitlmentsChangedHandler);

// Store original functions for restoration
originalOpenURLInDefaultBrowser = testWindow.Phoenix.app.openURLInDefaultBrowser;
Expand Down Expand Up @@ -117,6 +121,8 @@ define(function (require, exports, module) {

afterAll(async function () {
// Restore original functions
entitlementsService.off(entitlementsService.EVENT_ENTITLEMENTS_CHANGED,
LoginShared.entitlmentsChangedHandler);
testWindow.Phoenix.app.openURLInDefaultBrowser = originalOpenURLInDefaultBrowser;
testWindow.Phoenix.app.copyToClipboard = originalCopyToClipboard;

Expand Down
58 changes: 58 additions & 0 deletions test/spec/login-shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,20 +289,38 @@ define(function (require, exports, module) {
EntitlementsExports = _EntitlementsExports;
}

let entitlementsEventFired = false;
function entitlmentsChangedHandler() {
entitlementsEventFired = true;
}

function setupSharedTests() {

it("should complete login and logout flow", async function () {
entitlementsEventFired = false;

// Setup basic user mock
setupProUserMock(false);

// Perform full login flow
await performFullLoginFlow();
expect(LoginServiceExports.LoginService.isLoggedIn()).toBe(true);

// Wait for entitlements event to fire after login
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after login");
expect(entitlementsEventFired).toBe(true);

// Reset flag for logout test
entitlementsEventFired = false;

// Perform full logout flow
await performFullLogoutFlow();
expect(LoginServiceExports.LoginService.isLoggedIn()).toBe(false);
verifyProfileIconBlanked();

// Wait for entitlements event to fire after logout
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after logout");
expect(entitlementsEventFired).toBe(true);
});

it("should update profile icon after login", async function () {
Expand Down Expand Up @@ -392,6 +410,8 @@ define(function (require, exports, module) {
it("should show pro branding for user with pro subscription (expired trial)", async function () {
console.log("llgT: Starting pro user with expired trial test");

entitlementsEventFired = false;

// Setup: Pro subscription + expired trial
setupProUserMock(true);
await setupExpiredTrial();
Expand All @@ -408,6 +428,10 @@ define(function (require, exports, module) {
await performFullLoginFlow();
await verifyProBranding(true, "pro branding to appear after pro user login");

// Wait for entitlements event to fire after login
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after login");
expect(entitlementsEventFired).toBe(true);

// Verify entitlements API consistency for logged in pro user
await verifyIsInProTrialEntitlement(false, "pro user should not be in trial");
await verifyPlanEntitlements({ isSubscriber: true, paidSubscriber: true }, "pro user should have paid subscriber plan");
Expand All @@ -423,9 +447,16 @@ define(function (require, exports, module) {
// Close popup
$profileButton.trigger('click');

// Reset flag for logout test
entitlementsEventFired = false;

// Perform logout
await performFullLogoutFlow();

// Wait for entitlements event to fire after logout
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after logout");
expect(entitlementsEventFired).toBe(true);

// For user with pro subscription + expired trial:
// After logout, pro branding should disappear because:
// 1. No server entitlements (logged out)
Expand All @@ -441,6 +472,8 @@ define(function (require, exports, module) {
it("should show trial branding for user without pro subscription (active trial)", async function () {
console.log("llgT: Starting trial user test");

entitlementsEventFired = false;

// Setup: No pro subscription + active trial (15 days)
setupProUserMock(false);
await setupTrialState(15);
Expand All @@ -459,6 +492,10 @@ define(function (require, exports, module) {
// Verify pro branding remains after login
await verifyProBranding(true, "after trial user login");

// Wait for entitlements event to fire after login
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after login");
expect(entitlementsEventFired).toBe(true);

// Verify entitlements API consistency for logged in trial user
await verifyIsInProTrialEntitlement(true, "user should still be in trial after login");
await verifyPlanEntitlements({ isSubscriber: true, paidSubscriber: false }, "trial user should have isSubscriber true but paidSubscriber false");
Expand All @@ -474,9 +511,16 @@ define(function (require, exports, module) {
// Close popup
$profileButton.trigger('click');

// Reset flag for logout test
entitlementsEventFired = false;

// Perform logout
await performFullLogoutFlow();

// Wait for entitlements event to fire after logout
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after logout");
expect(entitlementsEventFired).toBe(true);

// Verify pro branding remains after logout (trial continues)
await verifyProBranding(true, "Trial branding to remain after logout");

Expand Down Expand Up @@ -541,6 +585,8 @@ define(function (require, exports, module) {
it("should show free branding for user without pro subscription (expired trial)", async function () {
console.log("llgT: Starting desktop trial user test");

entitlementsEventFired = false;

// Setup: No pro subscription + expired trial
setupProUserMock(false);
await setupExpiredTrial();
Expand All @@ -562,6 +608,10 @@ define(function (require, exports, module) {
// Verify pro branding remains after login
await verifyProBranding(false, "after trial free user login");

// Wait for entitlements event to fire after login
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after login");
expect(entitlementsEventFired).toBe(true);

// Verify entitlements API consistency for logged in free user
await verifyPlanEntitlements({ isSubscriber: false, paidSubscriber: false, name: testWindow.Strings.USER_FREE_PLAN_NAME_DO_NOT_TRANSLATE },
"free plan for logged in user with expired trial");
Expand All @@ -579,9 +629,16 @@ define(function (require, exports, module) {
// Close popup
$profileButton.trigger('click');

// Reset flag for logout test
entitlementsEventFired = false;

// Perform logout
await performFullLogoutFlow();

// Wait for entitlements event to fire after logout
await awaitsFor(() => entitlementsEventFired, "Entitlements event to fire after logout");
expect(entitlementsEventFired).toBe(true);

// Verify pro branding remains after logout (trial continues)
await verifyProBranding(false, "Trial branding to remain after logout");

Expand Down Expand Up @@ -921,6 +978,7 @@ define(function (require, exports, module) {
exports.popupToAppear = popupToAppear;
exports.performFullLogoutFlow = performFullLogoutFlow;
exports.verifyProfileIconBlanked = verifyProfileIconBlanked;
exports.entitlmentsChangedHandler = entitlmentsChangedHandler;
exports.VIEW_TRIAL_DAYS_LEFT = VIEW_TRIAL_DAYS_LEFT;
exports.VIEW_PHOENIX_PRO = VIEW_PHOENIX_PRO;
exports.VIEW_PHOENIX_FREE = VIEW_PHOENIX_FREE;
Expand Down
Loading