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
51 changes: 31 additions & 20 deletions src/extensionsIntegrated/Phoenix/guided-tour.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
/*
* GNU AGPL-3.0 License
*
* Copyright (c) 2021 - present core.ai . All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
* for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
*
*/
// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (c) 2021 - present core.ai. All rights reserved.

/*global logger*/

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("Guided Tour should have access to KernalModeTrust. Cannot boot without trust ring");
}

const NotificationUI = require("widgets/NotificationUI"),
Commands = require("command/Commands"),
Strings = require("strings"),
Expand Down Expand Up @@ -244,6 +235,23 @@ define(function (require, exports, module) {
PhStore.setItem(GUIDED_TOUR_LOCAL_STORAGE_KEY, JSON.stringify(userAlreadyDidAction));
}

async function _resolvePowerUserSurveyURL(surveyJson) {
try {
const isLoggedIn = KernalModeTrust.EntitlementsManager.isLoggedIn();
if(!isLoggedIn) {
return surveyJson.powerUser;
}
const paidSubscriber = await KernalModeTrust.EntitlementsManager.isPaidSubscriber();
if(paidSubscriber && surveyJson.powerUserPaid) {
return surveyJson.powerUserPaid;
}
return surveyJson.powerUserLoggedIn || surveyJson.powerUser;
} catch (e) {
logger.reportError(e, "Error resolving power user survey URL");
}
return surveyJson.powerUser;
}

async function _showSurveys() {
try {
if(!navigator.onLine){
Expand All @@ -258,6 +266,8 @@ define(function (require, exports, module) {
newUserShowDelayMS: surveyJSON.browser.newUserShowDelayMS || surveyJSON.newUserShowDelayMS,
newUserUseDialog: surveyJSON.browser.newUserUseDialog || surveyJSON.newUserUseDialog,
powerUser: surveyJSON.browser.powerUser || surveyJSON.powerUser,
powerUserPaid: surveyJSON.browser.powerUserPaid || surveyJSON.powerUserPaid,
powerUserLoggedIn: surveyJSON.browser.powerUserLoggedIn || surveyJSON.powerUserLoggedIn,
powerUserTitle: surveyJSON.browser.powerUserTitle || surveyJSON.powerUserTitle,
powerUserShowIntervalDays: surveyJSON.browser.powerUserShowIntervalDays
|| surveyJSON.powerUserShowIntervalDays,
Expand All @@ -266,7 +276,8 @@ define(function (require, exports, module) {
}
surveyJSON.newUser && _showFirstUseSurvey(surveyJSON.newUser, surveyJSON.newUserShowDelayMS,
surveyJSON.newUserTitle, surveyJSON.newUserUseDialog);
surveyJSON.powerUser && _showRepeatUserSurvey(surveyJSON.powerUser, surveyJSON.powerUserShowIntervalDays,
const powerUserSurveyURL = await _resolvePowerUserSurveyURL(surveyJSON);
powerUserSurveyURL && _showRepeatUserSurvey(powerUserSurveyURL, surveyJSON.powerUserShowIntervalDays,
surveyJSON.powerUserTitle, surveyJSON.powerUserUseDialog);
} catch (e) {
console.error("Error fetching survey link", surveyLinksURL, e);
Expand Down
17 changes: 16 additions & 1 deletion src/services/EntitlementsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ define(function (require, exports, module) {
* @returns {*}
*/
function isLoggedIn() {
return LoginService.isLoggedIn();
return LoginService && LoginService.isLoggedIn();
}

/**
Expand Down Expand Up @@ -109,6 +109,19 @@ define(function (require, exports, module) {
};
}

/**
* Checks if the current user is a paid subscriber (has purchased a plan, not trial users)
*
* @return {Promise<boolean>} A promise that resolves to true if the user is a paid subscriber, false otherwise.
*/
async function isPaidSubscriber() {
if(!isLoggedIn()) {
return false;
}
const planDetails = await getPlanDetails();
return !!planDetails.paidSubscriber;
}

/**
* Check if user is in a pro trial. IF the user is in pro trail, then `plan.isSubscriber` will always be true.
* @returns {Promise<boolean>} True if user is in pro trial, false otherwise
Expand Down Expand Up @@ -339,6 +352,7 @@ define(function (require, exports, module) {
EntitlementsService: EntitlementsManager,
isLoggedIn,
getPlanDetails,
isPaidSubscriber,
isInProTrial,
getTrialRemainingDays,
getRawEntitlements,
Expand All @@ -355,6 +369,7 @@ define(function (require, exports, module) {
EntitlementsManager.isLoggedIn = isLoggedIn;
EntitlementsManager.loginToAccount = loginToAccount;
EntitlementsManager.getPlanDetails = getPlanDetails;
EntitlementsManager.isPaidSubscriber = isPaidSubscriber;
EntitlementsManager.isInProTrial = isInProTrial;
EntitlementsManager.getTrialRemainingDays = getTrialRemainingDays;
EntitlementsManager.getRawEntitlements = getRawEntitlements;
Expand Down
67 changes: 67 additions & 0 deletions test/spec/login-shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ define(function (require, exports, module) {
}
}

async function verifyIsPaidSubscriber(expected, _testDescription) {
await awaitsFor(async ()=> {
const isPaidSub = await EntitlementsExports.isPaidSubscriber();
return isPaidSub === expected;
}, `paid subscriber to be ${expected}`);
}

async function verifyRawEntitlements(expected, _testDescription) {
const rawEntitlements = await EntitlementsExports.getRawEntitlements();

Expand Down Expand Up @@ -395,6 +402,7 @@ define(function (require, exports, module) {
// Verify entitlements API consistency for logged out state
await verifyIsInProTrialEntitlement(false, "no trial for logged out user with expired trial");
await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining");
await verifyIsPaidSubscriber(false, "logged out user should not be a paid subscriber");

// Perform login
await performFullLoginFlow();
Expand All @@ -403,6 +411,7 @@ define(function (require, exports, module) {
// 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");
await verifyIsPaidSubscriber(true, "pro user should be a paid subscriber");

// Check profile popup shows pro status (not trial)
const $profileButton = testWindow.$("#user-profile-button");
Expand All @@ -426,6 +435,7 @@ define(function (require, exports, module) {
// Verify entitlements API consistency after logout
await verifyRawEntitlements(null, "no raw entitlements when logged out");
await verifyIsInProTrialEntitlement(false, "no trial after logout");
await verifyIsPaidSubscriber(false, "logged out pro user should not be a paid subscriber (no server entitlements)");
});

it("should show trial branding for user without pro subscription (active trial)", async function () {
Expand All @@ -452,6 +462,7 @@ define(function (require, exports, module) {
// 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");
await verifyIsPaidSubscriber(false, "trial user should not be a paid subscriber");

// Check profile popup shows trial status
const $profileButton = testWindow.$("#user-profile-button");
Expand All @@ -472,6 +483,7 @@ define(function (require, exports, module) {
// Verify entitlements API consistency after logout (trial still active)
await verifyIsInProTrialEntitlement(true, "trial should persist after logout");
await verifyRawEntitlements(null, "no raw entitlements when logged out");
await verifyIsPaidSubscriber(false, "logged out trial user should not be a paid subscriber");

// Check profile popup still shows trial status
$profileButton.trigger('click');
Expand All @@ -496,6 +508,9 @@ define(function (require, exports, module) {
// Verify pro branding appears
await verifyProBranding(true, "Pro branding to appear for pro user");

// Verify entitlements API consistency for logged in pro user with trial
await verifyIsPaidSubscriber(true, "pro user with trial should be a paid subscriber");

// Check profile popup shows pro status (not trial text)
const $profileButton = testWindow.$("#user-profile-button");
$profileButton.trigger('click');
Expand All @@ -513,6 +528,7 @@ define(function (require, exports, module) {

// Verify pro branding remains due to trial (even though subscription is gone)
await verifyProBranding(true, "Pro branding should remain after logout as trial user");
await verifyIsPaidSubscriber(false, "logged out user should not be a paid subscriber (no server entitlements)");
$profileButton.trigger('click');
await popupToAppear(SIGNIN_POPUP);
await verifyProfilePopupContent(VIEW_TRIAL_DAYS_LEFT,
Expand All @@ -538,6 +554,7 @@ define(function (require, exports, module) {
await verifyIsInProTrialEntitlement(false, "no trial for user with expired trial");
await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining for expired trial");
await verifyLiveEditEntitlement({ activated: false }, "live edit deactivated for expired trial");
await verifyIsPaidSubscriber(false, "logged out free user should not be a paid subscriber");

// Perform login
await performFullLoginFlow();
Expand All @@ -550,6 +567,7 @@ define(function (require, exports, module) {
"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");
await verifyIsPaidSubscriber(false, "logged in free user should not be a paid subscriber");

// Check profile popup shows free plan status
const $profileButton = testWindow.$("#user-profile-button");
Expand All @@ -570,6 +588,7 @@ define(function (require, exports, module) {
// Verify entitlements API consistency after logout
await verifyRawEntitlements(null, "no raw entitlements when logged out");
await verifyIsInProTrialEntitlement(false, "no trial after logout");
await verifyIsPaidSubscriber(false, "logged out free user should not be a paid subscriber");

// Check profile popup still shows free plan status as trial expired
$profileButton.trigger('click');
Expand Down Expand Up @@ -598,6 +617,7 @@ define(function (require, exports, module) {
await verifyTrialRemainingDaysEntitlement(0, "no trial days remaining");
await verifyRawEntitlements(null, "no raw entitlements when logged out");
await verifyLiveEditEntitlement({ activated: false }, "live edit deactivated with no trial");
await verifyIsPaidSubscriber(false, "logged out user with expired entitlements should not be a paid subscriber");

// Perform login
await performFullLoginFlow();
Expand All @@ -615,6 +635,7 @@ define(function (require, exports, module) {
subscribeURL: testWindow.brackets.config.purchase_url,
upgradeToPlan: testWindow.brackets.config.main_pro_plan
}, "live edit deactivated with fallback URLs for expired entitlements");
await verifyIsPaidSubscriber(false, "logged in user with expired entitlements should not be a paid subscriber");

// Check profile popup shows free plan status
const $profileButton = testWindow.$("#user-profile-button");
Expand All @@ -635,6 +656,7 @@ define(function (require, exports, module) {
// Verify entitlements API consistency after logout
await verifyRawEntitlements(null, "no raw entitlements when logged out");
await verifyIsInProTrialEntitlement(false, "no trial after logout");
await verifyIsPaidSubscriber(false, "logged out user with expired entitlements should not be a paid subscriber");

// Check profile popup (signed out state)
$profileButton.trigger('click');
Expand Down Expand Up @@ -663,6 +685,7 @@ define(function (require, exports, module) {
await verifyTrialRemainingDaysEntitlement(10, "should have 10 trial days remaining");
await verifyRawEntitlements(null, "no raw entitlements when logged out");
await verifyLiveEditEntitlement({ activated: true }, "live edit activated via trial");
await verifyIsPaidSubscriber(false, "logged out trial user should not be a paid subscriber");

// Perform login
await performFullLoginFlow();
Expand All @@ -676,6 +699,7 @@ define(function (require, exports, module) {
await verifyIsInProTrialEntitlement(true, "user should still be in trial after login");
await verifyTrialRemainingDaysEntitlement(10, "trial days should remain 10 after login");
await verifyLiveEditEntitlement({ activated: true }, "live edit should be activated via trial override");
await verifyIsPaidSubscriber(false, "logged in trial user should not be a paid subscriber (expired entitlements)");

// Check profile popup shows trial status (not expired server entitlements)
const $profileButton = testWindow.$("#user-profile-button");
Expand All @@ -698,6 +722,7 @@ define(function (require, exports, module) {
await verifyTrialRemainingDaysEntitlement(10, "trial days should persist after logout");
await verifyRawEntitlements(null, "no raw entitlements when logged out");
await verifyLiveEditEntitlement({ activated: true }, "live edit still activated via trial after logout");
await verifyIsPaidSubscriber(false, "logged out trial user should not be a paid subscriber");

// Check profile popup still shows trial status
$profileButton.trigger('click');
Expand Down Expand Up @@ -752,6 +777,47 @@ define(function (require, exports, module) {
}
});

it("should test isPaidSubscriber API across different user states", async function () {
console.log("isPaidSubscriber: Testing API across different user states");

try {
// Test 1: Logged out user should not be a paid subscriber
await cleanupTrialState();
expect(LoginServiceExports.LoginService.isLoggedIn()).toBe(false);
await verifyIsPaidSubscriber(false, "logged out user should not be a paid subscriber");

// Test 2: Free user (no subscription) should not be a paid subscriber
setupProUserMock(false);
await performFullLoginFlow();
expect(LoginServiceExports.LoginService.isLoggedIn()).toBe(true);
await verifyIsPaidSubscriber(false, "free user should not be a paid subscriber");
await performFullLogoutFlow();

// Test 3: Trial user (no paid subscription) should not be a paid subscriber
setupProUserMock(false);
await setupTrialState(10);
await performFullLoginFlow();
expect(LoginServiceExports.LoginService.isLoggedIn()).toBe(true);
await verifyIsPaidSubscriber(false, "trial user should not be a paid subscriber");
await performFullLogoutFlow();
await cleanupTrialState();

// Test 4: Pro user with paid subscription should be a paid subscriber
setupProUserMock(true);
await performFullLoginFlow();
expect(LoginServiceExports.LoginService.isLoggedIn()).toBe(true);
await verifyIsPaidSubscriber(true, "pro user should be a paid subscriber");

// Test 5: After logout, should not be a paid subscriber (no server entitlements)
await performFullLogoutFlow();
expect(LoginServiceExports.LoginService.isLoggedIn()).toBe(false);
await verifyIsPaidSubscriber(false, "logged out pro user should not be a paid subscriber");

} finally {
await cleanupTrialState();
}
});

if (Phoenix.isNativeApp) {
it("should show device-licensed Pro branding and popup when not logged in", async function () {
console.log("llgT: Starting device license Pro branding test");
Expand Down Expand Up @@ -797,6 +863,7 @@ define(function (require, exports, module) {
);
await verifyIsInProTrialEntitlement(false, "device license is not a trial");
await verifyLiveEditEntitlement({ activated: true }, "live edit activated via device license");
await verifyIsPaidSubscriber(false, "educational device license should not be a paid subscriber (not logged in)");

// Verify raw entitlements are present (not null)
const rawEntitlements = await EntitlementsExports.getRawEntitlements();
Expand Down
Loading