Skip to content
Merged
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
212 changes: 111 additions & 101 deletions extension/chrome/elements/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,121 @@ import { Ui } from '../../js/common/browser/ui.js';
import { Lang } from '../../js/common/lang.js';
import { Api } from '../../js/common/api/api.js';
import { BrowserMsg, Bm } from '../../js/common/browser/browser-msg.js';
import { Backend, ProductName, Product } from '../../js/common/api/backend.js';
import { Backend, ProductName, Product, FcUuidAuth } from '../../js/common/api/backend.js';
import { Assert } from '../../js/common/assert.js';
import { XssSafeFactory } from '../../js/common/xss_safe_factory.js';
import { Xss } from '../../js/common/platform/xss.js';
import { Settings } from '../../js/common/settings.js';
import { View } from '../../js/common/view.js';

Catch.try(async () => {
View.run(class SubscribeView extends View {
private readonly acctEmail: string;
private readonly parentTabId: string;
private readonly placement: string | undefined;
private authInfo: FcUuidAuth | undefined;
private tabId: string | undefined;

Ui.event.protect();

const uncheckedUrlParams = Url.parse(['acctEmail', 'placement', 'parentTabId']);
const acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail');
const parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId');
const placement = Assert.urlParamRequire.oneof(uncheckedUrlParams, 'placement', ['settings', 'settings_compose', 'default', 'dialog', 'gmail', 'embedded', 'compose', undefined]);

const authInfo = await Store.authInfo(acctEmail);

const PRODUCTS: { [productName in ProductName]: Product } = {
private readonly PRODUCTS: { [productName in ProductName]: Product } = {
null: { id: null, method: null, name: null, level: null }, // tslint:disable-line:no-null-keyword
trial: { id: 'free_month', method: 'trial', name: 'trial', level: 'pro' },
advancedMonthly: { id: 'cu-adv-month', method: 'stripe', name: 'advanced_monthly', level: 'pro' },
};

const stripeCcEnteredHandler: Bm.AsyncResponselessHandler = async ({ token }: Bm.StripeResult) => {
$('.stripe_checkout').text('').css('display', 'none');
await subscribeAndHandleResult(PRODUCTS.advancedMonthly, token);
};
constructor() {
super();
const uncheckedUrlParams = Url.parse(['acctEmail', 'placement', 'parentTabId']);
this.acctEmail = Assert.urlParamRequire.string(uncheckedUrlParams, 'acctEmail');
this.parentTabId = Assert.urlParamRequire.string(uncheckedUrlParams, 'parentTabId');
this.placement = Assert.urlParamRequire.oneof(uncheckedUrlParams, 'placement', ['settings', 'settings_compose', 'default', 'dialog', 'gmail', 'embedded', 'compose', undefined]);
}

async render() {
Ui.event.protect();
if (this.placement === 'settings') {
$('#content').removeClass('dialog').css({ 'margin-top': 0, 'margin-bottom': 30 });
$('.line.button_padding').css('padding', 0);
} else {
$('body').css('overflow', 'hidden');
}
$('.list_table').css('display', 'block');
$('#content').css('display', 'block');
await this.renderSubscriptionDetails();
this.tabId = await BrowserMsg.requiredTabId();
$('.stripe_checkout')
.html(`${Lang.account.creditOrDebit}<br><br>${new XssSafeFactory(this.acctEmail, this.tabId).embeddedStripeCheckout()}<br>${Ui.retryLink('back')}`); // xss-safe-factory
}

setHandlers() {
$('.action_close').click(this.setHandler(() => this.closeDialog()));
$('.action_show_stripe').click(this.setHandler(() => this.showStripeHandler()));
$('.action_contact_page').click(this.setHandler(() => BrowserMsg.send.bg.settings({ page: '/chrome/settings/modules/contact_page.htm', acctEmail: this.acctEmail })));
$('.action_get_trial').click(this.setHandlerPrevent('parallel', async (target, done) => {
await this.subscribeAndHandleResult(this.PRODUCTS.trial, undefined);
done();
}));
BrowserMsg.addListener('stripe_result', (res) => this.stripeCcEnteredHandler(res as Bm.StripeResult));
BrowserMsg.listen(this.tabId!);
}

private async renderSubscriptionDetails() {
this.authInfo = await Store.authInfo(this.acctEmail);
try {
await Backend.accountGet(this.authInfo);
await Backend.getSubscriptionWithoutLogin(this.acctEmail);
} catch (e) {
if (Api.err.isAuthErr(e)) {
Xss.sanitizeRender('#content', `Not logged in. ${Ui.retryLink()}`);
Settings.offerToLoginWithPopupShowModalOnErr(this.acctEmail, () => window.location.reload());
} else if (Api.err.isNetErr(e)) {
Xss.sanitizeRender('#content', `Failed to load due to internet connection. ${Ui.retryLink()}`);
} else {
Catch.reportErr(e);
Xss.sanitizeRender('#content', `Unknown error happened when fetching account info. ${Ui.retryLink()}`);
}
}
const subscription = await Store.subscription(this.acctEmail); // updated in getSubscriptionWithoutLogin
if (!subscription.active) {
if (subscription.level && subscription.expire) {
if (subscription.method === 'trial') {
$('.status').text('Your trial has expired on ' + Str.datetimeToDate(subscription.expire) + '. Upgrade now to continue using FlowCrypt Advanced.');
} else if (subscription.method === 'group') {
$('.status').text('Your group licensing is due for renewal. Please check with company leadership.');
} else {
$('.status').text('Your subscription has ended on ' + subscription.expire + '. Renew now to continue using FlowCrypt Advanced.');
}
$('.action_get_trial').css('display', 'none');
$('.action_show_stripe').removeClass('gray').addClass('green');
} else {
$('.status').text('After the trial, your account will automatically switch to Free Forever.');
}
} else if (subscription.active && subscription.method === 'trial') {
Xss.sanitizeRender('.status',
'After the trial, your account will automatically switch to Free Forever.<br/><br/>You can subscribe now to stay on FlowCrypt Advanced. It\'s $5 a month.');
}
if (subscription.active) {
if (subscription.method === 'trial') {
$('.list_table').css('display', 'none');
$('.action_get_trial').css('display', 'none');
$('.action_show_stripe').removeClass('gray').addClass('green');
} else {
Xss.sanitizeRender('#content', `<div class="line">${Lang.account.alreadyUpgraded}</div><div class="line"><div class="button green long action_close">close</div></div>`);
$('.action_close').click(this.setHandler(() => this.closeDialog()));
}
}
}

private showStripeHandler() {
$('.status').text('You are subscribing to a $5 monthly payment for FlowCrypt Advanced.');
$('.hide_on_checkout').css('display', 'none');
$('.stripe_checkout').css('display', 'block');
}

const subscribeAndHandleResult = async (chosenProduct: Product, source: string | undefined) => {
private async subscribeAndHandleResult(chosenProduct: Product, source: string | undefined) {
try {
const response = await Backend.accountSubscribe(authInfo, chosenProduct.id!, chosenProduct.method!, source);
const response = await Backend.accountSubscribe(this.authInfo!, chosenProduct.id!, chosenProduct.method!, source);
if (response.subscription.level === chosenProduct.level && response.subscription.method === chosenProduct.method) {
await Ui.modal.info('Successfully upgraded to FlowCrypt Advanced.');
closeDialog();
this.closeDialog();
}
throw new Error('Something went wrong when upgrading (values don\'t match), please email human@flowcrypt.com to get this resolved.');
} catch (e) {
Expand All @@ -60,92 +141,21 @@ Catch.try(async () => {
Catch.reportErr(e);
}
}
};

const closeDialog = () => {
$('body').attr('data-test-state', 'closed'); // used by automated tests
if (placement === 'settings_compose') {
window.close();
} else if (placement === 'settings') {
BrowserMsg.send.reload(parentTabId, {});
} else {
BrowserMsg.send.closeDialog(parentTabId);
}
};

try {
await Backend.accountGet(authInfo);
await Backend.getSubscriptionWithoutLogin(acctEmail);
} catch (e) {
if (Api.err.isAuthErr(e)) {
Xss.sanitizeRender('#content', `Not logged in. ${Ui.retryLink()}`);
Settings.offerToLoginWithPopupShowModalOnErr(acctEmail, () => window.location.reload());
} else if (Api.err.isNetErr(e)) {
Xss.sanitizeRender('#content', `Failed to load due to internet connection. ${Ui.retryLink()}`);
} else {
Catch.reportErr(e);
Xss.sanitizeRender('#content', `Unknown error happened when fetching account info. ${Ui.retryLink()}`);
}
}

const subscription = await Store.subscription(acctEmail); // updated in getSubscriptionWithoutLogin

if (placement === 'settings') {
$('#content').removeClass('dialog').css({ 'margin-top': 0, 'margin-bottom': 30 });
$('.line.button_padding').css('padding', 0);
} else {
$('body').css('overflow', 'hidden');
private async stripeCcEnteredHandler({ token }: Bm.StripeResult) {
$('.stripe_checkout').text('').css('display', 'none');
await this.subscribeAndHandleResult(this.PRODUCTS.advancedMonthly, token);
}
$('.list_table').css('display', 'block');
$('#content').css('display', 'block');

$('.action_show_stripe').click(Ui.event.handle(() => {
$('.status').text('You are subscribing to a $5 monthly payment for FlowCrypt Advanced.');
$('.hide_on_checkout').css('display', 'none');
$('.stripe_checkout').css('display', 'block');
}));

$('.action_contact_page').click(Ui.event.handle(() => BrowserMsg.send.bg.settings({ page: '/chrome/settings/modules/contact_page.htm', acctEmail })));

$('.action_close').click(Ui.event.handle(closeDialog));

$('.action_get_trial').click(Ui.event.prevent('parallel', async (target, done) => {
await subscribeAndHandleResult(PRODUCTS.trial, undefined);
done();
}));

if (!subscription.active) {
if (subscription.level && subscription.expire) {
if (subscription.method === 'trial') {
$('.status').text('Your trial has expired on ' + Str.datetimeToDate(subscription.expire) + '. Upgrade now to continue using FlowCrypt Advanced.');
} else if (subscription.method === 'group') {
$('.status').text('Your group licensing is due for renewal. Please check with company leadership.');
} else {
$('.status').text('Your subscription has ended on ' + subscription.expire + '. Renew now to continue using FlowCrypt Advanced.');
}
$('.action_get_trial').css('display', 'none');
$('.action_show_stripe').removeClass('gray').addClass('green');
} else {
$('.status').text('After the trial, your account will automatically switch to Free Forever.');
}
} else if (subscription.active && subscription.method === 'trial') {
Xss.sanitizeRender('.status', 'After the trial, your account will automatically switch to Free Forever.<br/><br/>You can subscribe now to stay on FlowCrypt Advanced. It\'s $5 a month.');
}
if (subscription.active) {
if (subscription.method === 'trial') {
$('.list_table').css('display', 'none');
$('.action_get_trial').css('display', 'none');
$('.action_show_stripe').removeClass('gray').addClass('green');
private closeDialog() {
$('body').attr('data-test-state', 'closed'); // used by automated tests
if (this.placement === 'settings_compose') {
window.close();
} else if (this.placement === 'settings') {
BrowserMsg.send.reload(this.parentTabId, {});
} else {
Xss.sanitizeRender('#content', `<div class="line">${Lang.account.alreadyUpgraded}</div><div class="line"><div class="button green long action_close">close</div></div>`);
$('.action_close').click(Ui.event.handle(() => closeDialog()));
BrowserMsg.send.closeDialog(this.parentTabId);
}
}

const tabId = await BrowserMsg.requiredTabId();
BrowserMsg.addListener('stripe_result', stripeCcEnteredHandler);
BrowserMsg.listen(tabId);

$('.stripe_checkout').html(`${Lang.account.creditOrDebit}<br><br>${new XssSafeFactory(acctEmail, tabId).embeddedStripeCheckout()}<br>${Ui.retryLink('back')}`); // xss-safe-factory

})();
});