diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index 57b3f42..c8c0260 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -122,5 +122,44 @@
"searchServer": {
"message": "Search Server",
"description": "Placeholder for the Input field Where users can search the Serverlist"
+ },
+ "titleSubscribeNow": {
+ "message": "Subscribe to Mozilla VPN"
+ },
+ "bodySubscribeNow": {
+ "message": "No subscription found. Click the button below to subscribe to Mozilla VPN.",
+ "description": "Body of an Error Message, shown if the user is trying to use the Extension without a Subscription."
+ },
+ "btnSubscribeNow": {
+ "message": "Subscribe now",
+ "description": "Call to Action button for users to subscribe to Mozilla VPN"
+ },
+ "getHelp": {
+ "message": "Get Help",
+ "description": "Text of a link pointing to a relevant help page"
+ },
+ "headerSignedOut": {
+ "message": "Sign in to your Mozilla account"
+ },
+ "bodySignedOut": {
+ "message": "You’re currently signed out of your Mozilla account. To use the Mozilla VPN extension, please open the desktop app to sign in first."
+ },
+ "btnOpenVpn": {
+ "message": "Open Mozilla VPN"
+ },
+ "headerInstallMsg": {
+ "message": "Install Mozilla VPN",
+ "description": "Header if a VPN installation was not found"
+ },
+ "bodyInstallMsg": {
+ "message": "In order to use the Mozilla VPN extension, you must first download Mozilla VPN on your desktop."
+ },
+ "bodyInstallMsgFooter": {
+ "message": "Note that Mozilla VPN is a paid subscription service.",
+ "description": "This is a footnote for 'bodyInstallMsg' "
+ },
+ "btnDownloadNow": {
+ "message": "Download now",
+ "description": "This is a call to action button to download the VPN."
}
}
diff --git a/src/assets/img/message-header.svg b/src/assets/img/message-header.svg
new file mode 100644
index 0000000..c2fe861
--- /dev/null
+++ b/src/assets/img/message-header.svg
@@ -0,0 +1,44 @@
+
diff --git a/src/assets/img/message-install.svg b/src/assets/img/message-install.svg
new file mode 100644
index 0000000..db932dd
--- /dev/null
+++ b/src/assets/img/message-install.svg
@@ -0,0 +1,335 @@
+
diff --git a/src/assets/img/message-signin.svg b/src/assets/img/message-signin.svg
new file mode 100644
index 0000000..f00bde7
--- /dev/null
+++ b/src/assets/img/message-signin.svg
@@ -0,0 +1,26 @@
+
diff --git a/src/background/vpncontroller/states.js b/src/background/vpncontroller/states.js
index 1b0da8f..cc31f9c 100644
--- a/src/background/vpncontroller/states.js
+++ b/src/background/vpncontroller/states.js
@@ -14,17 +14,25 @@ export const REQUEST_TYPES = [
"disabled_apps",
"status",
"deactivate",
+ "focus",
+ "openAuth",
];
export class VPNState {
// Name of the current state
state = "";
+ // Whether the Native Message adapter exists
+ installed = true;
// If the Native Message adapter is alive
alive = false;
// True if the VPN is enabled.
connected = false;
// True if firefox is split-tunneled
isExcluded = false;
+ // True if a subscription is found
+ subscribed = true;
+ // True if it is authenticated
+ authenticated = false;
/**
* A socks:// url to connect to
* to bypass the vpn.
@@ -53,17 +61,42 @@ export class VPNState {
export class StateVPNUnavailable extends VPNState {
state = "Unavailable";
alive = false;
+ installed = false;
+}
+export class StateVPNClosed extends VPNState {
+ state = "Closed";
+ alive = false;
+ installed = true;
connected = false;
}
+/**
+ * Helper base class to imply the vpn process is installed and
+ * running
+ */
+class StateVPNOpened extends VPNState {
+ alive = true;
+ installed = true;
+}
+export class StateVPNSignedOut extends StateVPNOpened {
+ state = "SignedOut";
+ authenticated = false;
+}
+
+export class StateVPNSubscriptionNeeded extends StateVPNSignedOut {
+ state = "SubscriptionNeeded";
+ subscribed = false;
+ authenticated = true;
+}
+
/**
* This state is used if the VPN Client is
* alive but the Connection is Disabled
*/
-export class StateVPNDisabled extends VPNState {
+export class StateVPNDisabled extends StateVPNSubscriptionNeeded {
state = "Disabled";
- alive = true;
connected = false;
+ subscribed = true;
/**
*
@@ -81,7 +114,7 @@ export class StateVPNDisabled extends VPNState {
* This state is used if the VPN Client is
* alive but the Connection is Disabled
*/
-export class StateVPNEnabled extends VPNState {
+export class StateVPNEnabled extends StateVPNDisabled {
/**
*
* @param {string|boolean} aloophole - False if loophole is not supported,
@@ -89,14 +122,12 @@ export class StateVPNEnabled extends VPNState {
* @param {ServerCountry | undefined } exitServerCountry
*/
constructor(exitServerCity, exitServerCountry, aloophole, connectedSince) {
- super();
- this.exitServerCity = exitServerCity;
- this.exitServerCountry = exitServerCountry;
+ super(exitServerCity, exitServerCountry);
this.loophole = aloophole;
this.connectedSince = connectedSince;
}
state = "Enabled";
- alive = true;
+ subscribed = true;
connected = true;
}
diff --git a/src/background/vpncontroller/vpncontroller.js b/src/background/vpncontroller/vpncontroller.js
index c52183c..838f83c 100644
--- a/src/background/vpncontroller/vpncontroller.js
+++ b/src/background/vpncontroller/vpncontroller.js
@@ -18,9 +18,12 @@ import {
StateVPNUnavailable,
StateVPNEnabled,
StateVPNDisabled,
+ StateVPNSubscriptionNeeded,
REQUEST_TYPES,
ServerCountry,
vpnStatusResponse,
+ StateVPNClosed,
+ StateVPNSignedOut,
} from "./states.js";
const log = Logger.logger("TabHandler");
@@ -77,16 +80,17 @@ export class VPNController extends Component {
// invalid proxy connection.
this.#port.onDisconnect.addListener(() => {
this.#increaseIsolationKey();
- this.#mState.value = new StateVPNUnavailable();
+ this.#mState.value = new StateVPNClosed();
});
} catch (e) {
+ // If we get an exception here it is super likely the VPN is simply not installed.
log(e);
this.#mState.value = new StateVPNUnavailable();
}
}
async init() {
- this.#mState.value = new StateVPNUnavailable();
+ this.#mState.value = new StateVPNClosed();
this.#mServers.value = await fromStorage(
browser.storage.local,
MOZILLA_VPN_SERVERS_KEY,
@@ -116,13 +120,14 @@ export class VPNController extends Component {
log(e);
// @ts-ignore
if (e.toString() === "Attempt to postMessage on disconnected port") {
- this.#mState.value = new StateVPNUnavailable();
+ this.#mState.value = new StateVPNClosed();
}
}
}
// Handle responses from MozillaVPN client
async handleResponse(response) {
+ console.log(response);
if (!response.t) {
// The VPN Client always sends a ".t : string"
// to determing the message type.
@@ -156,7 +161,7 @@ export class VPNController extends Component {
// We can only get 2 types of messages right now: client-down/up
if (response.status && response.status === "vpn-client-down") {
if (this.#mState.value.alive) {
- this.#mState.value = new StateVPNUnavailable();
+ this.#mState.value = new StateVPNClosed();
}
return;
}
@@ -275,6 +280,16 @@ export function fromVPNStatusResponse(
return;
}
const status = response.status;
+ const appState = status.app;
+ if (["StateInitialize", "StateAuthenticating"].includes(appState)) {
+ return new StateVPNSignedOut();
+ }
+
+ if (appState === "StateSubscriptionNeeded") {
+ return new StateVPNSubscriptionNeeded();
+ }
+
+ //
const controllerState = status.vpn;
const connectedSince = (() => {
if (!status.connectedSince) {
diff --git a/src/components/conditional-view.js b/src/components/conditional-view.js
new file mode 100644
index 0000000..d35ad42
--- /dev/null
+++ b/src/components/conditional-view.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html, LitElement } from "../vendor/lit-all.min.js";
+
+/**
+ * `ConditionalView`
+ *
+ * Takes N elements each with a slot="" attribute,
+ * the active rendered view can be controlled using slotName="slot"
+ * if no view matches "slotName" the slot named "default" will be rendered.
+ *
+ *
+ * Hidden
+ * This is rendered
+ *
${bodyText}
` : bodyText; + + class Temp extends MessageScreen { + connectedCallback() { + super.connectedCallback(); + this.titleHeader = tr("productName"); + this.img = img; + this.heading = heading; + this.primaryAction = primaryAction; + this.onPrimaryAction = onPrimaryAction; + this.secondaryAction = secondarAction; + this.onSecondaryAction = onSecondaryAction; + render(body, this); + } + } + customElements.define(tag, Temp); +}; + +const sendToApp = (customElement, command = "") => { + customElement.dispatchEvent( + new CustomEvent("requestMessage", { + bubbles: true, + detail: command, + }) + ); +}; + +defineMessageScreen( + "subcribenow-message-screen", + "message-header.svg", + "Subscribe to Mozilla VPN", + tr("bodySubscribeNow"), + tr("btnSubscribeNow"), + (elm) => sendToApp(elm, "focus") +); + +defineMessageScreen( + "signin-message-screen", + "message-signin.svg", + tr("headerSignedOut"), + tr("bodySignedOut"), + tr("btnOpenVpn"), + (elm) => { + sendToApp(elm, "focus"); + sendToApp(elm, "openAuth"); + } +); + +defineMessageScreen( + "install-message-screen", + "message-signin.svg", + tr("headerInstallMsg"), + html` +${tr("bodyInstallMsg")}
+${tr("bodyInstallMsgFooter")}
+ `, + tr("btnDownloadNow"), + () => { + open("https://www.mozilla.org/products/vpn/download/"); + } +); diff --git a/src/ui/browserAction/backend.js b/src/ui/browserAction/backend.js index f4a49b5..2c7a1d0 100644 --- a/src/ui/browserAction/backend.js +++ b/src/ui/browserAction/backend.js @@ -1,5 +1,28 @@ import { getExposedObject } from "../../shared/ipc.js"; +/** + * Import Types + * + * @typedef {import("../../shared/property.js").IBindable} IBindable + * @typedef {import("../../background/vpncontroller/states.js").ServerCountry} ServerCountry + * @typedef {import("../../background/vpncontroller/states.js").ServerCity} ServerCity + * @typedef {import("../../background/vpncontroller/states.js").VPNState} State + * @typedef {Array