Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Message Screeens #57

Merged
merged 7 commits into from
Sep 16, 2024
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
39 changes: 39 additions & 0 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
}
44 changes: 44 additions & 0 deletions src/assets/img/message-header.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
335 changes: 335 additions & 0 deletions src/assets/img/message-install.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions src/assets/img/message-signin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 38 additions & 7 deletions src/background/vpncontroller/states.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;

/**
*
Expand All @@ -81,22 +114,20 @@ 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,
* @param {ServerCity | undefined} exitServerCity
* @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;
}

Expand Down
23 changes: 19 additions & 4 deletions src/background/vpncontroller/vpncontroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ import {
StateVPNUnavailable,
StateVPNEnabled,
StateVPNDisabled,
StateVPNSubscriptionNeeded,
REQUEST_TYPES,
ServerCountry,
vpnStatusResponse,
StateVPNClosed,
StateVPNSignedOut,
} from "./states.js";

const log = Logger.logger("TabHandler");
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down
49 changes: 49 additions & 0 deletions src/components/conditional-view.js
Original file line number Diff line number Diff line change
@@ -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.
*
*
* <conditional-view slotName="b">
* <h1 slot="a">Hidden</h1>
* <h1 slot="b">This is rendered</h1>
* </conditional-view>
*/

export class ConditionalView extends LitElement {
static properties = {
slotName: { reflect: true },
};
constructor() {
super();
this.slotName = "default";
}

hasSlot(slotName) {
return Array.from(this.children).some((e) => {
return e.slot === slotName;
});
}
getTargetSlot() {
const slot = this.slotName;
if (slot == "") {
return "default";
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC blank slot names should be caught at check at L40 which also returns "default" so I don't think we need this block.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could have a subelement with slot="" and therefore match slotName="".
Also just no need to iterate over the children if it is empty, as i want it to use "default" in that case.

if (!this.hasSlot(slot)) {
return "default";
}
return slot;
}
render() {
return html` <slot name=${this.getTargetSlot()}></slot> `;
}
}
customElements.define("conditional-view", ConditionalView);
Loading