MozillaVPN Integration

This commit is contained in:
Lesley Norton
2021-10-20 12:52:41 -05:00
parent de80fe9050
commit 1c0f2d8c5c
19 changed files with 2656 additions and 394 deletions
+11 -3
View File
@@ -186,12 +186,20 @@ window.assignManager = {
async handleProxifiedRequest(requestInfo) {
// The following blocks potentially dangerous requests for privacy that come without a tabId
if(requestInfo.tabId === -1)
return Utils.getBogusProxy();
// Dupe of Utils.DEFAULT_PROXY, which was occasionally and unreliably
// not being found on startup and causing significant UI grief.
if(requestInfo.tabId === -1) {
return {
value: Object.freeze({type: "direct"}),
writable: false,
enumerable: true,
configurable: false
};
}
const tab = await browser.tabs.get(requestInfo.tabId);
const proxy = await proxifiedContainers.retrieveFromBackground(tab.cookieStoreId);
return proxy;
},
+6 -6
View File
@@ -41,9 +41,11 @@ const backgroundLogic = {
async deleteContainer(userContextId, removed = false) {
await this._closeTabs(userContextId);
if (!removed) {
await browser.contextualIdentities.remove(this.cookieStoreId(userContextId));
}
assignManager.deleteContainer(userContextId);
// Now remove the identity->proxy association in proxifiedContainers also
@@ -59,18 +61,16 @@ const backgroundLogic = {
this.cookieStoreId(options.userContextId),
options.params
);
proxifiedContainers.set(this.cookieStoreId(options.userContextId), options.proxy);
} else {
donePromise = browser.contextualIdentities.create(options.params);
// We cannot yet access the new cookieStoreId via this.cookieStoreId(...), so we take this from the resolved promise
donePromise.then((identity) => {
proxifiedContainers.set(identity.cookieStoreId, options.proxy);
(identity.cookieStoreId, options.proxy);
}).catch(() => {
// Empty because this should never happen theoretically.
});
}
await donePromise;
},
@@ -160,7 +160,7 @@ const backgroundLogic = {
}
return await identityState.storageArea.set(cookieStoreId, containerState);
} catch (error) {
console.error(`No container: ${cookieStoreId}`);
// console.error(`No container: ${cookieStoreId}`);
}
},
@@ -376,4 +376,4 @@ const backgroundLogic = {
};
backgroundLogic.init();
backgroundLogic.init();
+1 -1
View File
@@ -1,4 +1,4 @@
const MAJOR_VERSIONS = ["2.3.0", "2.4.0", "6.2.0"];
const MAJOR_VERSIONS = ["2.3.0", "2.4.0", "6.2.0", "8.0.0"];
const badge = {
async init() {
const currentWindow = await browser.windows.getCurrent();
+1
View File
@@ -16,6 +16,7 @@
<script type="text/javascript" src="../utils.js"></script>
<script type="text/javascript" src="../proxified-containers.js"></script>
<script type="text/javascript" src="backgroundLogic.js"></script>
<script type="text/javascript" src="mozillaVpnBackground.js"></script>
<script type="text/javascript" src="assignManager.js"></script>
<script type="text/javascript" src="badge.js"></script>
<script type="text/javascript" src="identityState.js"></script>
+13 -4
View File
@@ -83,18 +83,27 @@ const messageHandler = {
break;
case "reloadInContainer":
response = assignManager.reloadPageInContainer(
m.url,
m.currentUserContextId,
m.newUserContextId,
m.url,
m.currentUserContextId,
m.newUserContextId,
m.tabIndex,
m.active,
true
);
break;
case "mozillaVpnAttemptPort":
MozillaVPN_Background.maybeInitPort();
break;
case "getMozillaVpnServers":
MozillaVPN_Background.postToApp("servers");
break;
case "getMozillaVpnStatus":
response = MozillaVPN_Background.postToApp("status");
break;
case "assignAndReloadInContainer":
tab = await assignManager.reloadPageInContainer(
m.url,
m.currentUserContextId,
m.currentUserContextId,
m.newUserContextId,
m.tabIndex,
m.active,
+84
View File
@@ -0,0 +1,84 @@
const MozillaVPN_Background = {
MOZILLA_VPN_INSTALLED_KEY: "mozillaVpnInstalled",
MOZILLA_VPN_CONNECTED_KEY: "mozillaVpnConnected",
MOZILLA_VPN_COLLAPSE_EDIT_CONTAINER_TOUT_KEY: "mozillaVpnCollapseEditContainerTout",
MOZILLA_VPN_HIDE_MAIN_TOUT_KEY: "mozillaVpnHideMainTout",
MOZILLA_VPN_SERVERS_KEY: "mozillaVpnServers",
async maybeInitPort() {
if (this.port && this.port.error === null) {
return;
}
try {
/*
Find a way to not spam the console when MozillaVPN client is not installed
File at path ".../../MozillaVPN/..." is not executable.` thrown by resource://gre/modules/Subprocess.jsm:152`
Which does is not caught by this try/catch
*/
this.port = await browser.runtime.connectNative("mozillavpn");
await browser.storage.local.set({ [this.MOZILLA_VPN_INSTALLED_KEY]: true});
this.port.onMessage.addListener(this.handleResponse);
this.postToApp("status");
this.postToApp("servers");
} catch(e) {
browser.storage.local.set({ [this.MOZILLA_VPN_INSTALLED_KEY]: false });
browser.storage.local.set({ [this.MOZILLA_VPN_CONNECTED_KEY]: false });
}
},
async init() {
const mozillaVpnConnected = await browser.storage.local.get(this.MOZILLA_VPN_CONNECTED_KEY);
if (typeof(mozillaVpnConnected) === "undefined") {
browser.storage.local.set({ [this.MOZILLA_VPN_CONNECTED_KEY]: false });
browser.storage.local.set({ [this.MOZILLA_VPN_INSTALLED_KEY]: false });
browser.storage.local.set({ [this.MOZILLA_VPN_SERVERS_KEY]: [] });
browser.storage.local.set({ [this.MOZILLA_VPN_HIDE_MAIN_TOUT_KEY]: false });
browser.storage.local.set({ [this.MOZILLA_VPN_COLLAPSE_EDIT_CONTAINER_TOUT_KEY]: false });
}
this.maybeInitPort();
},
// Post messages to MozillaVPN client
postToApp(message) {
try {
this.port.postMessage({t: message});
} catch(e) {
if (e.message === "Attempt to postMessage on disconnected port") {
browser.storage.local.set({ [this.MOZILLA_VPN_INSTALLED_KEY]: false });
browser.storage.local.set({ [this.MOZILLA_VPN_CONNECTED_KEY]: false });
}
}
},
// Handle responses from MozillaVPN client
async handleResponse(response) {
if (response.error && response.error === "vpn-client-down") {
browser.storage.local.set({ [MozillaVPN_Background.MOZILLA_VPN_CONNECTED_KEY]: false });
return;
}
if (response.servers) {
const servers = response.servers.countries;
browser.storage.local.set({ [MozillaVPN_Background.MOZILLA_VPN_SERVERS_KEY]: servers});
return;
}
if (response.status && response.status.vpn) {
browser.storage.local.set({ [MozillaVPN_Background.MOZILLA_VPN_INSTALLED_KEY]: true });
const status = response.status.vpn;
if (status === "StateOn") {
browser.storage.local.set({ [MozillaVPN_Background.MOZILLA_VPN_CONNECTED_KEY]: true });
}
if (status === "StateOff" || status === "StateDisconnecting") {
browser.storage.local.set({ [MozillaVPN_Background.MOZILLA_VPN_CONNECTED_KEY]: false });
}
}
}
};
MozillaVPN_Background.init();
+240
View File
@@ -0,0 +1,240 @@
const MozillaVPN = {
async handleContainerList(identities) {
const { mozillaVpnConnected } = await browser.storage.local.get("mozillaVpnConnected");
const { mozillaVpnInstalled } = await browser.storage.local.get("mozillaVpnInstalled");
this.handleStatusIndicatorsInContainerLists(mozillaVpnInstalled);
const proxies = await this.getProxies(identities);
if (Object.keys(proxies).length === 0) {
return;
}
for (const el of document.querySelectorAll("[data-cookie-store-id]")) {
const cookieStoreId = el.dataset.cookieStoreId;
const { proxy } = proxies[cookieStoreId];
if (typeof(proxy) !== "undefined") {
const flag = el.querySelector(".flag-img");
if (proxy.countryCode) {
flag.src = `/img/flags/${proxy.countryCode.toUpperCase()}.png`;
}
if (typeof(proxy.mozProxyEnabled) === "undefined" && typeof(proxy.countryCode) !== "undefined") {
flag.classList.add("proxy-disabled");
}
if (!mozillaVpnConnected && proxy.mozProxyEnabled) {
flag.classList.add("proxy-unavailable");
el.querySelector(".menu-item-name").dataset.mozProxyWarning = "proxy-unavailable";
}
}
}
},
async setStatusIndicatorIcons(mozillaVpnInstalled) {
const { mozillaVpnConnected } = await browser.storage.local.get("mozillaVpnConnected");
const statusIconEls = document.querySelectorAll(".moz-vpn-connection-status-indicator");
if (!mozillaVpnInstalled) {
statusIconEls.forEach(el => {
el.style.backgroundImage = "none";
if (el.querySelector(".tooltip")) {
el.querySelector(".tooltip").textContent = "";
}
el.textContent = "";
});
return;
}
const connectedIndicatorSrc = "url(./img/moz-vpn-connected.svg)";
const disconnectedIndicatorSrc = "url(./img/moz-vpn-disconnected.svg)";
const connectionStatusStringId = mozillaVpnConnected ? "moz-vpn-connected" : "moz-vpn-disconnected";
const connectionStatusLocalizedString = browser.i18n.getMessage(connectionStatusStringId);
statusIconEls.forEach(el => {
el.style.backgroundImage = mozillaVpnConnected ? connectedIndicatorSrc : disconnectedIndicatorSrc;
if (el.querySelector(".tooltip")) {
el.querySelector(".tooltip").textContent = connectionStatusLocalizedString;
} else {
el.textContent = connectionStatusLocalizedString;
}
});
},
async handleStatusIndicatorsInContainerLists(mozillaVpnInstalled) {
const mozVpnLogotypes = document.querySelectorAll(".moz-vpn-logotype.vpn-status-container-list");
try {
if (!mozillaVpnInstalled) {
mozVpnLogotypes.forEach(el => {
el.style.display = "none";
});
return;
}
mozVpnLogotypes.forEach(el => {
el.style.display = "flex";
el.classList.remove("display-none");
});
this.setStatusIndicatorIcons(mozillaVpnInstalled);
} catch (e) {
mozVpnLogotypes.forEach(el => {
el.style.display = "none";
});
return;
}
},
handleMozillaCtaClick(buttonIdentifier) {
browser.tabs.create({
url: MozillaVPN.attachUtmParameters("https://www.mozilla.org/products/vpn", buttonIdentifier),
});
},
getRandomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
},
proxyIsDisabled(proxy) {
return (
// Mozilla VPN proxy is disabled, last location data is stored
(proxy.mozProxyEnabled === undefined && proxy.countryCode !== undefined && proxy.cityName !== undefined) ||
// Mozilla VPN proxy is enabled but Mozilla VPN is not connected
proxy.mozProxyEnabled !== undefined
);
},
attachUtmParameters(baseUrl, utmContent) {
const url = new URL(baseUrl);
const utmParameters = {
utm_source: "multi.account.containers",
utm_medium: "mac-browser-addon",
utm_content: utmContent,
utm_campaign: "vpn-better-together",
};
for (const param in utmParameters) {
url.searchParams.append(param, utmParameters[param]);
}
return url.href;
},
async getProxies(identities) {
const { mozillaVpnInstalled } = await browser.storage.local.get("mozillaVpnInstalled");
const proxies = {};
if (mozillaVpnInstalled) {
for (const identity of identities) {
try {
const proxy = await proxifiedContainers.retrieve(identity.cookieStoreId);
proxies[identity.cookieStoreId] = proxy;
} catch (e) {
proxies[identity.cookieStoreId] = {};
}
}
}
return proxies;
},
getMozillaProxyInfoObj () {
return {
countryCode: undefined,
cityName: undefined,
mozProxyEnabled: undefined
};
},
async getProxyWarnings(proxyObj) {
const { mozillaVpnConnected } = await browser.storage.local.get("mozillaVpnConnected");
if (!proxyObj) {
return "";
}
const { proxy } = proxyObj;
if (typeof(proxy) === "undefined") {
return "";
}
if (typeof(proxy.mozProxyEnabled) !== "undefined" && !mozillaVpnConnected) {
return "proxy-unavailable";
}
},
async getFlag(proxyObj) {
const { mozillaVpnConnected } = await browser.storage.local.get("mozillaVpnConnected");
const { mozillaVpnInstalled } = await browser.storage.local.get("mozillaVpnInstalled");
const flag = {
imgCode: "default",
elemClasses: "display-none",
imgAlt: "",
};
if (!proxyObj) {
return flag;
}
const { proxy } = proxyObj;
if (typeof(proxy) === "undefined" || !mozillaVpnInstalled) {
return flag;
}
if (mozillaVpnInstalled && typeof(proxy.cityName) !== "undefined") {
flag.imgCode = proxy.countryCode.toUpperCase();
flag.imgAlt = proxy.cityName;
flag.elemClasses = typeof(proxy.mozProxyEnabled) === "undefined" || !mozillaVpnConnected ? "proxy-disabled" : "";
}
return flag;
},
getProxy(countryCode, cityName, mozProxyEnabled, mozillaVpnServers) {
const selectedServerCountry = mozillaVpnServers.find(({code}) => code === countryCode);
const selectedServerCity = selectedServerCountry.cities.find(({name}) => name === cityName);
const proxyServer = this.pickServerBasedOnWeight(selectedServerCity.servers);
return proxifiedContainers.parseProxy(
this.makeProxyString(proxyServer.socksName),
{
countryCode: countryCode,
cityName: cityName,
mozProxyEnabled,
}
);
},
makeProxyString(socksName) {
return `socks://${socksName}.mullvad.net:1080`;
},
async pickRandomServer() {
const { mozillaVpnServers } = await browser.storage.local.get("mozillaVpnServers");
const randomInteger = this.getRandomInteger(0, mozillaVpnServers.length - 1);
const randomServerCountry = mozillaVpnServers[randomInteger];
return {
randomServerCountryCode: randomServerCountry.code,
randomServerCityName: randomServerCountry.cities[0].name,
};
},
pickServerBasedOnWeight(serverList) {
const filteredServerList = serverList.filter(server => typeof(server.socksName) !== "undefined" && server.socksName !== "");
const sumWeight = filteredServerList.reduce((sum, { weight }) => sum + weight, 0);
let randomInteger = this.getRandomInteger(0, sumWeight);
let nextServer = {};
for (const server of filteredServerList) {
if (server.weight >= randomInteger) {
return nextServer = server;
}
randomInteger = (randomInteger - server.weight);
}
return nextServer;
}
};
window.MozillaVPN = MozillaVPN;
+10 -8
View File
@@ -1,22 +1,23 @@
async function init() {
const fragment = document.createDocumentFragment();
const identities = await browser.contextualIdentities.query({});
identities.forEach(identity => {
for (const identity of identities) {
const tr = document.createElement("tr");
tr.classList.add("menu-item", "hover-highlight");
tr.setAttribute("data-cookie-store-id", identity.cookieStoreId);
const td = document.createElement("td");
td.innerHTML = Utils.escaped`
td.innerHTML = Utils.escaped`
<div class="menu-icon">
<div class="usercontext-icon"
data-identity-icon="${identity.icon}"
data-identity-color="${identity.color}">
</div>
</div>
<span class="menu-text">${identity.name}</span>`;
<span class="menu-text">${identity.name}</span>
<img alt="" class="page-action-flag flag-img" src="/img/flags/.png"/>
`;
tr.appendChild(td);
fragment.appendChild(tr);
@@ -24,12 +25,13 @@ async function init() {
Utils.alwaysOpenInContainer(identity);
window.close();
});
});
}
const list = document.querySelector("#picker-identities-list");
list.innerHTML = "";
list.appendChild(fragment);
MozillaVPN.handleContainerList(identities);
}
init();
+693 -79
View File
File diff suppressed because it is too large Load Diff
+18 -7
View File
@@ -2,9 +2,13 @@
proxifiedContainers = {
// Slightly modified version of 'retrieve' which returns a direct proxy whenever an error is met.
retrieveFromBackground(cookieStoreId = null) {
async retrieveFromBackground(cookieStoreId = null) {
return new Promise((resolve, reject) => {
proxifiedContainers.retrieve(cookieStoreId).then((success) => {
// TODO consider better methods of handling containers with MozillaVPN proxy
// configs when MozillaVPN is not connected. Currently we are only messaging this
// in the main panel.
// if (mozProxyIsEnabled && !mozillaVpnConnected) { something better here ? }
resolve(success.proxy);
}, function() {
resolve(Utils.DEFAULT_PROXY);
@@ -31,7 +35,6 @@ proxifiedContainers = {
// 4. Normal operation - if the cookieStoreId exists in the map, we can simply resolve with the correct proxy value
const results_array = results["proxifiedContainersKey"];
if (Object.getOwnPropertyNames(results).length === 0) {
reject({
error: "uninitialized",
@@ -76,6 +79,7 @@ proxifiedContainers = {
proxifiedContainersKey: proxifiedContainersStore
});
resolve(proxy);
}
@@ -110,13 +114,22 @@ proxifiedContainers = {
},
//Parses a proxy description string of the format type://host[:port] or type://username:password@host[:port] (port is optional)
parseProxy(proxy_str) {
parseProxy(proxy_str, mozillaVpnData = null) {
const proxyRegexp = /(?<type>(https?)|(socks4?)):\/\/(\b(?<username>\w+):(?<password>\w+)@)?(?<host>((?:\d{1,3}\.){3}\d{1,3}\b)|(\b([\w.-]+)(\.([\w.-]+))+))(:(?<port>\d+))?/;
if (proxyRegexp.test(proxy_str) !== true) {
return false;
}
const matches = proxyRegexp.exec(proxy_str);
return matches.groups;
if (mozillaVpnData && mozillaVpnData.mozProxyEnabled === undefined) {
matches.groups.type = "direct";
}
if (!mozillaVpnData) {
mozillaVpnData = MozillaVPN.getMozillaProxyInfoObj();
}
return {...matches.groups,...mozillaVpnData};
},
// Deletes the proxy information object for a specified cookieStoreId [useful for cleaning]
@@ -126,9 +139,7 @@ proxifiedContainers = {
proxifiedContainers.retrieve().then((proxifiedContainersStore) => {
const index = proxifiedContainersStore.findIndex(i => i.cookieStoreId === cookieStoreId);
if (index === -1) {
reject({error: "not-found", message: `Container '${cookieStoreId}' not found.`});
} else {
if (index !== -1) {
proxifiedContainersStore.splice(index, 1);
}
+1 -1
View File
@@ -165,7 +165,7 @@ const Utils = {
assignedUserContextId,
false
);
}
},
};
window.Utils = Utils;