Skip to content

Commit

Permalink
allow disabling @ProtonMail upsell ads, closes #629
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimiry committed Aug 31, 2023
1 parent 33b555d commit bc05a93
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 13 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "electron-mail",
"description": "Unofficial ProtonMail Desktop App",
"version": "5.1.8",
"version": "5.2.0",
"author": "Vladimir Yakovlev <desktop-app@protonmail.ch>",
"license": "GPL-3.0",
"homepage": "https://github.com/vladimiry/ElectronMail",
Expand Down
48 changes: 48 additions & 0 deletions patches/protonmail/common-4.patch
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,51 @@ index 36bd0c712..c2fb3681c 100644
if (doesNotSupportEarlyAccessVersion()) {
return;
}

diff --git a/applications/mail/src/app/hooks/useShowUpsellBanner.ts b/applications/mail/src/app/hooks/useShowUpsellBanner.ts
index b76334920b..2501a4d4ad 100644
--- a/applications/mail/src/app/hooks/useShowUpsellBanner.ts
+++ b/applications/mail/src/app/hooks/useShowUpsellBanner.ts
@@ -33,6 +33,7 @@ const useShowUpsellBanner = (labelID: string, otherBannerDisplayed: boolean) =>
- No other banner is shown in the message list
- If a value is found in the localStorage that should trigger a new display
*/
+ /* <electron-mail-mark> */
const canDisplayUpsellBanner =
!user.hasPaidMail &&
Date.now() > threeDaysAfterCreationDate &&
@@ -40,6 +41,7 @@ const useShowUpsellBanner = (labelID: string, otherBannerDisplayed: boolean) =>
needToShowUpsellBanner.current &&
!otherBannerDisplayed &&
showAgain;
+ /* </electron-mail-mark> */

const handleDismissBanner = () => {
// Set the ref to false so that we hide the banner and update the localStorage value
@@ -72,6 +74,10 @@ const useShowUpsellBanner = (labelID: string, otherBannerDisplayed: boolean) =>
}
}, []);

+ if (___ELECTRON_MAIL_PROTON_SUPPRESS_UPSELL_ADS_PLACEHOLDER___) {
+ return { canDisplayUpsellBanner: false, needToShowUpsellBanner, handleDismissBanner };
+ }
+
return { canDisplayUpsellBanner, needToShowUpsellBanner, handleDismissBanner };
};

diff --git a/packages/components/containers/heading/PrivateHeader.tsx b/packages/components/containers/heading/PrivateHeader.tsx
index 399435457d..7cf035baca 100644
--- a/packages/components/containers/heading/PrivateHeader.tsx
+++ b/packages/components/containers/heading/PrivateHeader.tsx
@@ -74,7 +74,10 @@ const PrivateHeader = ({
<TopNavbar>
<TopNavbarList>
{isNarrow && searchDropdown ? <TopNavbarListItem>{searchDropdown}</TopNavbarListItem> : null}
- {upsellButton !== undefined ? upsellButton : <TopNavbarUpsell app={app} />}
+ {___ELECTRON_MAIL_PROTON_SUPPRESS_UPSELL_ADS_PLACEHOLDER___
+ ? null
+ : (upsellButton !== undefined ? upsellButton : <TopNavbarUpsell app={app} />)
+ }
{feedbackButton ? <TopNavbarListItem noShrink>{feedbackButton}</TopNavbarListItem> : null}
{contactsButton ? <TopNavbarListItem noShrink>{contactsButton}</TopNavbarListItem> : null}
{settingsButton ? <TopNavbarListItem noShrink>{settingsButton}</TopNavbarListItem> : null}
40 changes: 28 additions & 12 deletions src/electron-main/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import _logger from "electron-log";
import {app, protocol, ProtocolResponse, Session} from "electron";
import {first} from "rxjs/operators";
import fs from "fs";
import {lastValueFrom, Observable} from "rxjs";
import {lookup as lookupMimeType} from "mrmime";
import path from "path";
import pathIsInside from "path-is-inside";
import {promisify} from "util";
import {Readable} from "stream";
import {URL} from "@cliqz/url-parser";

import {AccountConfig} from "src/shared/model/account";
import type {AccountConfig} from "src/shared/model/account";
import {AccountSessionAppData, Context} from "src/electron-main/model";
import {assertEntryUrl} from "src/electron-main/util";
import {Config} from "src/shared/model/options";
import {curryFunctionMembers} from "src/shared/util";
import {LOCAL_WEBCLIENT_DIR_NAME, LOCAL_WEBCLIENT_SCHEME_NAME, WEB_PROTOCOL_DIR, WEB_PROTOCOL_SCHEME} from "src/shared/const";
import {PROTON_API_URL_PLACEHOLDER} from "src/shared/const/proton-url";
import {PROTON_API_URL_PLACEHOLDER, PROTON_SUPPRESS_UPSELL_ADS_PLACEHOLDER} from "src/shared/const/proton-url";
import {PROVIDER_REPO_MAP} from "src/shared/const/proton-apps";
import {resolveProtonApiOrigin, resolveProtonAppTypeFromUrlHref} from "src/shared/util/proton-url";

Expand Down Expand Up @@ -112,19 +115,28 @@ async function resolveFileSystemResourceLocation(directory: string, requestUrl:

const readFileAndInjectApiUrl = async (
fileLocation: string,
{entryUrl: accountEntryUrl, requestUrl}: Readonly<Pick<AccountConfig, "entryUrl">> & { requestUrl: URL },
{
entryUrl: accountEntryUrl,
requestUrl,
config$,
}: Readonly<Pick<AccountConfig, "entryUrl">> & { requestUrl: URL, config$: Observable<Config> },
): Promise<Readable> => {
assertEntryUrl(accountEntryUrl);

const config = await lastValueFrom(config$.pipe(first()));
const {suppressUpsellMessages} = config;

return Readable.from(
Buffer.from(
(await fsAsync.readFile(fileLocation)).toString().replaceAll(
PROTON_API_URL_PLACEHOLDER,
resolveProtonApiOrigin({
accountEntryUrl,
subdomain: PROVIDER_REPO_MAP[resolveProtonAppTypeFromUrlHref(requestUrl.href).type].apiSubdomain,
}),
),
(await fsAsync.readFile(fileLocation)).toString()
.replaceAll(
PROTON_API_URL_PLACEHOLDER,
resolveProtonApiOrigin({
accountEntryUrl,
subdomain: PROVIDER_REPO_MAP[resolveProtonAppTypeFromUrlHref(requestUrl.href).type].apiSubdomain,
}),
)
.replaceAll(PROTON_SUPPRESS_UPSELL_ADS_PLACEHOLDER, String(suppressUpsellMessages)),
),
);
};
Expand Down Expand Up @@ -155,10 +167,14 @@ export async function registerAccountSessionProtocols(
if (!mimeType) {
throw new Error(`Failed to resolve "${nameof(mimeType)}" by the ${JSON.stringify({resourceExt})} value`);
}
const data: Readable = resourceExt === ".js" || resourceExt === ".html"
const data: Readable = [".js", ".cjs", ".mjs", ".html"].includes(resourceExt)
? await readFileAndInjectApiUrl(
resourceLocation,
{entryUrl: session._electron_mail_data_.entryUrl, requestUrl},
{
entryUrl: session._electron_mail_data_.entryUrl,
requestUrl,
config$: ctx.config$,
},
)
: fs.createReadStream(resourceLocation);
const callbackResponse: ProtocolResponse = {mimeType, data};
Expand Down
4 changes: 4 additions & 0 deletions src/shared/const/proton-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const PROTON_API_ENTRY_VALUE_PREFIX = "local:::";
// WARN the value also used for patching the web clients
export const PROTON_API_URL_PLACEHOLDER = "___ELECTRON_MAIL_PROTON_API_ENTRY_URL_PLACEHOLDER___";

// WARN the value also used for patching the web clients
export const PROTON_SUPPRESS_UPSELL_ADS_PLACEHOLDER
= "___ELECTRON_MAIL_PROTON_SUPPRESS_UPSELL_ADS_PLACEHOLDER___";

export const PROTON_API_ENTRY_PRIMARY_VALUE = "https://mail.proton.me";

export const PROTON_API_ENTRY_PRIMARY_DOMAIN_NAME = resolvePrimaryDomainNameFromUrlHostname(
Expand Down
2 changes: 2 additions & 0 deletions src/shared/model/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface Config extends BaseConfig, Partial<StoreModel.StoreEntity> {
layoutMode: (typeof import("src/shared/const").LAYOUT_MODES)[number]["value"]
logLevel: LogLevel
startHidden: boolean
suppressUpsellMessages: boolean
themeSource: "system" | "dark" | "light"
unreadNotifications: boolean
zoomFactor: number
Expand All @@ -96,6 +97,7 @@ export type BaseConfig = Pick<Config,
| "logLevel"
| "spellcheck"
| "startHidden"
| "suppressUpsellMessages"
| "themeSource"
| "unreadNotifications"
| "zoomFactor">;
Expand Down
1 change: 1 addition & 0 deletions src/shared/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export function initialConfig(): Config {
themeSource: "system",
unreadNotifications: true,
zoomFactor: ZOOM_FACTOR_DEFAULT,
suppressUpsellMessages: false,
};
}
}
Expand Down
24 changes: 24 additions & 0 deletions src/web/browser-window/app/_options/base-settings.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,30 @@
Check for update and notify
</label>
</div>
<div class="custom-control custom-switch">
<input
class="custom-control-input"
formControlName="suppressUpsellMessages"
id="suppressUpsellMessagesCheckbox"
type="checkbox"
>
<label class="custom-control-label" for="suppressUpsellMessagesCheckbox">
Disable Proton upsell ads
<i
[placement]="'bottom'"
[popover]="suppressUpsellMessagesTemplate"
class="fa fa-info-circle text-warning"
container="body"
triggers="mouseenter:mouseleave"
></i>
<a href="{{ PACKAGE_GITHUB_PROJECT_URL }}/issues/629">#629</a>
<ng-template #suppressUpsellMessagesTemplate>
<p>
App restart or account page reload required for the option to take effect.
</p>
</ng-template>
</label>
</div>
<div class="custom-control custom-switch">
<input
class="custom-control-input"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class BaseSettingsComponent implements OnInit, OnDestroy {
),
spellcheck: new FormControl(),
startHidden: new FormControl(),
suppressUpsellMessages: new FormControl(),
themeSource: new FormControl(),
unreadNotifications: new FormControl(),
zoomFactor: new FormControl(),
Expand Down

0 comments on commit bc05a93

Please sign in to comment.