Skip to content

Commit

Permalink
Add Message Screeens (#57)
Browse files Browse the repository at this point in the history
This PR adds a view-router we can use to switch out the browser action
main element based on ... things.
Also i add a generic message-screen factory, so we can simply add more
with one func call :)



https://github.com/user-attachments/assets/e159f60e-d9be-40f3-84fa-979f02fd5ec9
  • Loading branch information
strseb authored Sep 16, 2024
1 parent 1ab707f commit 2d80597
Show file tree
Hide file tree
Showing 15 changed files with 981 additions and 25 deletions.
39 changes: 39 additions & 0 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,5 +118,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";
}
if (!this.hasSlot(slot)) {
return "default";
}
return slot;
}
render() {
return html` <slot name=${this.getTargetSlot()}></slot> `;
}
}
customElements.define("conditional-view", ConditionalView);
Loading

0 comments on commit 2d80597

Please sign in to comment.