Use new extension setup process

This commit is contained in:
Carlos Quintana
2022-05-17 12:31:47 +02:00
parent 2a8c23d736
commit af7333cdc8
9 changed files with 276 additions and 481 deletions
+2 -2
View File
@@ -16,14 +16,14 @@ const extractExtensionData = () => {
return {
name: extPackageJson.name + (isBeta ? '-beta' : '-release'),
version: extPackageJson.version + (isBeta ? ('.' + betaRev) : ''),
}
};
};
const makeDestZipDirIfNotExists = () => {
if(!fs.existsSync(DEST_ZIP_DIR)) {
fs.mkdirSync(DEST_ZIP_DIR);
}
}
};
const buildZip = (src, dist, zipFilename) => {
console.info(`Building ${zipFilename}...`);
+35 -1
View File
@@ -1,5 +1,5 @@
import browser from "webextension-polyfill";
import APIService from "../popup/APIService";
import APIService, { API_ROUTE } from "../popup/APIService";
import SLStorage from "../popup/SLStorage";
import Onboarding from "./onboarding";
import "./content-script";
@@ -9,6 +9,8 @@ import {
handleOnClickContextMenu,
generateAliasHandlerJS,
} from "./context-menu";
import axios from "axios";
import Utils from "../popup/Utils";
global.isBackgroundJS = true;
@@ -26,6 +28,36 @@ async function handleGetAppSettings() {
};
}
async function handleExtensionSetup() {
const apiUrl = await SLStorage.get(SLStorage.SETTINGS.API_URL);
try {
const url = apiUrl + API_ROUTE.GET_API_KEY_FROM_COOKIE.path;
const res = await axios.post(url, {
device: Utils.getDeviceName(),
});
const apiKey = res.data.api_key;
if (apiKey) {
await SLStorage.set(SLStorage.SETTINGS.API_KEY, apiKey);
const currentTab = await browser.tabs.query({
active: true,
currentWindow: true,
});
const url = `${apiUrl}/onboarding/final`;
await browser.tabs.update(currentTab[0].id, {
url,
});
} else {
console.error("Received null API Key");
}
} catch (e) {
// Probably the user is not logged in
console.error(e);
}
}
/**
* Register onMessage listener
*/
@@ -34,6 +66,8 @@ browser.runtime.onMessage.addListener(async function (request, sender) {
return await handleNewRandomAlias(request.currentUrl);
} else if (request.tag === "GET_APP_SETTINGS") {
return await handleGetAppSettings();
} else if (request.tag === "EXTENSION_SETUP") {
return await handleExtensionSetup();
}
});
+3 -20
View File
@@ -1,32 +1,15 @@
import browser from "webextension-polyfill";
import SLStorage from "../popup/SLStorage";
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
function initService() {
browser.runtime.onInstalled.addListener(async function ({ reason }) {
if (reason === "install") {
await SLStorage.clear();
const apiUrl = await SLStorage.get(SLStorage.SETTINGS.API_URL);
await browser.tabs.create({
url: browser.runtime.getURL("/onboarding/index.html"),
});
// listen for post-setup screen
browser.cookies.onChanged.addListener(async function (info) {
const { removed, cookie } = info;
if (!removed && cookie.name === "setup_done") {
const currentTab = await browser.tabs.query({
active: true,
currentWindow: true,
});
await delay(100);
await browser.tabs.update(currentTab[0].id, {
url: browser.runtime.getURL("/onboarding/index.html#step3"),
});
}
url: `${apiUrl}/onboarding`,
});
}
});
+236 -213
View File
@@ -1,214 +1,12 @@
if (!window.hasSLButton) {
window.hasSLButton = true;
let SLSettings = {};
const InputTools = {
isLoading: false,
// store tracked input elements
trackedElements: [],
init(target) {
InputTools.queryEmailInputAndApply(target, (element) => {
if (!InputTools.isValidEmailInput(element)) {
return;
}
// ignore if this elements has already been tracked
const i = InputTools.trackedElements.indexOf(element);
if (i === -1) {
InputTools.trackedElements.push(element);
InputTools.addSLButtonToInput(element);
}
});
},
destroy(target) {
InputTools.queryEmailInputAndApply(target, (element) => {
// remove element from tracking list
const i = InputTools.trackedElements.indexOf(element);
if (i !== -1) {
InputTools.trackedElements.splice(i, 1);
}
});
},
queryEmailInputAndApply(target, actionFunction) {
if (!target.querySelectorAll) return;
const elements = target.querySelectorAll(
"input[type='email'],input[name*='email'],input[id*='email']"
);
for (const element of elements) {
actionFunction(element);
}
},
isValidEmailInput(element) {
const style = getComputedStyle(element);
return (
// check if element is visible
style.visibility !== "hidden" &&
style.display !== "none" &&
style.opacity !== "0" &&
style.pointerEvents === "auto" &&
// check if element is not disabled
!element.disabled &&
// for example, we must filter out a checkbox with name*=email
// check if element is text or email input
(element.type === "text" || element.type === "email")
);
},
addSLButtonToInput(inputElem) {
// create wrapper for SL button
const btnWrapper = InputTools.newDiv("sl-button-wrapper");
const inputSumHeight = inputElem.getBoundingClientRect().height + "px";
btnWrapper.style.height = inputSumHeight;
btnWrapper.style.width = inputSumHeight;
document.body.appendChild(btnWrapper);
// create the SL button
const slButton = InputTools.newDiv("sl-button");
slButton.addEventListener("click", function () {
InputTools.handleOnClickSLButton(inputElem, slButton);
});
slButton.style.height = inputSumHeight;
slButton.style.width = inputSumHeight;
btnWrapper.appendChild(slButton);
InputTools.placeBtnToTheRightOfElement(inputElem, btnWrapper);
},
newDiv(...className) {
const div = document.createElement("div");
div.classList.add(...className);
return div;
},
placeBtnToTheRightOfElement(inputElem, btnWrapper) {
let intervalId = 0;
function updatePosition() {
// check is element is removed from trackedElements
const i = InputTools.trackedElements.indexOf(inputElem);
if (i === -1) {
btnWrapper.parentNode.removeChild(btnWrapper);
clearInterval(intervalId);
}
// get dimension & position of input
const inputCoords = inputElem.getBoundingClientRect();
const inputStyle = getComputedStyle(inputElem);
const elemWidth = InputTools.dimensionToInt(btnWrapper.style.width);
const pageXOffset = window.pageXOffset;
const pageYOffset = window.pageYOffset;
const buttonXOffset =
SLSettings.SLButtonPosition === "right-inside"
? -elemWidth * 1.02
: elemWidth * 0.02;
// calculate elem position
const left =
InputTools.sumPixel([
inputCoords.left,
pageXOffset,
inputElem.offsetWidth,
buttonXOffset,
-inputStyle.paddingRight,
]) + "px";
const top = InputTools.sumPixel([inputCoords.top, pageYOffset]) + "px";
if (btnWrapper.style.left !== left) {
btnWrapper.style.left = left;
}
if (btnWrapper.style.top !== top) {
btnWrapper.style.top = top;
}
}
intervalId = setInterval(updatePosition, 200);
updatePosition();
},
async handleOnClickSLButton(inputElem, slButton) {
if (InputTools.isLoading) {
return;
}
InputTools.isLoading = true;
slButton.classList.add("loading");
let res = await sendMessageToBackground("NEW_RANDOM_ALIAS", {
currentUrl: window.location.href,
});
if (res.error) {
alert("SimpleLogin Error: " + res.error);
res = { alias: "" };
}
InputTools.isLoading = false;
slButton.classList.remove("loading");
inputElem.value = res.alias;
},
sumPixel(dimensions) {
let sum = 0;
for (const dim of dimensions) {
sum += !isNaN(dim) ? dim : InputTools.dimensionToInt(dim);
}
return sum;
},
dimensionToInt(dim) {
try {
const pixel = parseFloat(dim.replace(/[^0-9.]+/g, ""));
return isNaN(pixel) ? 0 : pixel;
} catch (e) {
return 0;
}
},
};
const MutationObserver =
window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver;
/**
* Add DOM mutations listener
*/
function addMutationObserver() {
const mutationObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
for (const addedNode of mutation.addedNodes) {
// add SLButton for newly added nodes
InputTools.init(addedNode);
}
for (const removedNode of mutation.removedNodes) {
// destroy SLButton for removed nodes
InputTools.destroy(removedNode);
}
});
});
const target = document.body;
if (!target) return;
mutationObserver.observe(target, {
childList: true,
subtree: true,
});
}
if (!window._hasExecutedSlExtension) {
window._hasExecutedSlExtension = true;
/**
* Send message to background.js and resolve with the response
* @param {string} tag
* @param {object} data
*/
function sendMessageToBackground(tag, data = null) {
const sendMessageToBackground = (tag, data = null) => {
const _browser = window.chrome || browser;
return new Promise((resolve) => {
try {
@@ -226,15 +24,240 @@ if (!window.hasSLButton) {
console.error(e);
}
});
}
};
async function initModule() {
SLSettings = await sendMessageToBackground("GET_APP_SETTINGS");
if (SLSettings.showSLButton) {
InputTools.init(document);
addMutationObserver();
const slButtonLogic = async () => {
if (!window.hasSLButton) {
window.hasSLButton = true;
const InputTools = {
isLoading: false,
// store tracked input elements
trackedElements: [],
init(target) {
InputTools.queryEmailInputAndApply(target, (element) => {
if (!InputTools.isValidEmailInput(element)) {
return;
}
// ignore if this elements has already been tracked
const i = InputTools.trackedElements.indexOf(element);
if (i === -1) {
InputTools.trackedElements.push(element);
InputTools.addSLButtonToInput(element);
}
});
},
destroy(target) {
InputTools.queryEmailInputAndApply(target, (element) => {
// remove element from tracking list
const i = InputTools.trackedElements.indexOf(element);
if (i !== -1) {
InputTools.trackedElements.splice(i, 1);
}
});
},
queryEmailInputAndApply(target, actionFunction) {
if (!target.querySelectorAll) return;
const elements = target.querySelectorAll(
"input[type='email'],input[name*='email'],input[id*='email']"
);
for (const element of elements) {
actionFunction(element);
}
},
isValidEmailInput(element) {
const style = getComputedStyle(element);
return (
// check if element is visible
style.visibility !== "hidden" &&
style.display !== "none" &&
style.opacity !== "0" &&
style.pointerEvents === "auto" &&
// check if element is not disabled
!element.disabled &&
// for example, we must filter out a checkbox with name*=email
// check if element is text or email input
(element.type === "text" || element.type === "email")
);
},
addSLButtonToInput(inputElem) {
// create wrapper for SL button
const btnWrapper = InputTools.newDiv("sl-button-wrapper");
const inputSumHeight = inputElem.getBoundingClientRect().height + "px";
btnWrapper.style.height = inputSumHeight;
btnWrapper.style.width = inputSumHeight;
document.body.appendChild(btnWrapper);
// create the SL button
const slButton = InputTools.newDiv("sl-button");
slButton.addEventListener("click", function () {
InputTools.handleOnClickSLButton(inputElem, slButton);
});
slButton.style.height = inputSumHeight;
slButton.style.width = inputSumHeight;
btnWrapper.appendChild(slButton);
InputTools.placeBtnToTheRightOfElement(inputElem, btnWrapper);
},
newDiv(...className) {
const div = document.createElement("div");
div.classList.add(...className);
return div;
},
placeBtnToTheRightOfElement(inputElem, btnWrapper) {
let intervalId = 0;
function updatePosition() {
// check is element is removed from trackedElements
const i = InputTools.trackedElements.indexOf(inputElem);
if (i === -1) {
btnWrapper.parentNode.removeChild(btnWrapper);
clearInterval(intervalId);
}
// get dimension & position of input
const inputCoords = inputElem.getBoundingClientRect();
const inputStyle = getComputedStyle(inputElem);
const elemWidth = InputTools.dimensionToInt(btnWrapper.style.width);
const pageXOffset = window.pageXOffset;
const pageYOffset = window.pageYOffset;
const buttonXOffset =
SLSettings.SLButtonPosition === "right-inside"
? -elemWidth * 1.02
: elemWidth * 0.02;
// calculate elem position
const left =
InputTools.sumPixel([
inputCoords.left,
pageXOffset,
inputElem.offsetWidth,
buttonXOffset,
-inputStyle.paddingRight,
]) + "px";
const top = InputTools.sumPixel([inputCoords.top, pageYOffset]) + "px";
if (btnWrapper.style.left !== left) {
btnWrapper.style.left = left;
}
if (btnWrapper.style.top !== top) {
btnWrapper.style.top = top;
}
}
intervalId = setInterval(updatePosition, 200);
updatePosition();
},
async handleOnClickSLButton(inputElem, slButton) {
if (InputTools.isLoading) {
return;
}
InputTools.isLoading = true;
slButton.classList.add("loading");
let res = await sendMessageToBackground("NEW_RANDOM_ALIAS", {
currentUrl: window.location.href,
});
if (res.error) {
alert("SimpleLogin Error: " + res.error);
res = { alias: "" };
}
InputTools.isLoading = false;
slButton.classList.remove("loading");
inputElem.value = res.alias;
},
sumPixel(dimensions) {
let sum = 0;
for (const dim of dimensions) {
sum += !isNaN(dim) ? dim : InputTools.dimensionToInt(dim);
}
return sum;
},
dimensionToInt(dim) {
try {
const pixel = parseFloat(dim.replace(/[^0-9.]+/g, ""));
return isNaN(pixel) ? 0 : pixel;
} catch (e) {
return 0;
}
},
};
const MutationObserver =
window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver;
/**
* Add DOM mutations listener
*/
const addMutationObserver = () => {
const mutationObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
for (const addedNode of mutation.addedNodes) {
// add SLButton for newly added nodes
InputTools.init(addedNode);
}
for (const removedNode of mutation.removedNodes) {
// destroy SLButton for removed nodes
InputTools.destroy(removedNode);
}
});
});
const target = document.body;
if (!target) return;
mutationObserver.observe(target, {
childList: true,
subtree: true,
});
};
const SLSettings = await sendMessageToBackground("GET_APP_SETTINGS");
if (SLSettings.showSLButton) {
InputTools.init(document);
addMutationObserver();
}
}
}
};
initModule();
const slRegisterListener = () => {
if (!window.hasSlListenerRegistered) {
window.hasSlListenerRegistered = true;
/**
* Callback called for every event
* @param {MessageEvent<any>} event
*/
const onEvent = async (event) => {
if (event.source !== window) return;
if (event.data.tag && (event.data.tag === "PERFORM_EXTENSION_SETUP")) {
await sendMessageToBackground("EXTENSION_SETUP");
}
};
window.addEventListener("message", onEvent);
}
};
slButtonLogic();
slRegisterListener();
}
-151
View File
@@ -1,151 +0,0 @@
<template>
<div class="app">
<div class="row">
<div class="col-3 screen-left">
<div class="left-content">
<p :class="{ active: step === 2 }" @click="toStep(2)">Account</p>
<p :class="{ active: step === 3 }" @click="toStep(3)">Done</p>
</div>
</div>
<div class="col-6 screen-right">
<div class="right-content">
<div class="header">
<img
alt="SimpleLogin logo"
class="sl-logo"
src="/images/horizontal-logo.svg"
/>
</div>
<div class="content" v-if="step === 1">
<h5>Thank you for installing SimpleLogin!</h5>
<p>
SimpleLogin is an open-source email alias solution to defend your
personal email address against spams, phishing and to protect your
privacy.
</p>
<br />
<button @click="nextStep()" class="btn btn-primary">
Get started!
</button>
</div>
<div class="content mb-2" v-if="step === 2">
<h5>Create an account</h5>
<p>
If you already have an account on SimpleLogin, you can skip this
step.
</p>
<br />
<a :href="url.createNewAccount" class="btn btn-primary">
Create a new account
</a>
<a :href="url.login" class="btn btn-outline-primary">
I already have account
</a>
</div>
<div class="content" v-if="step === 3">
<h5>It's all set!</h5>
<p>
To start using SimpleLogin, please click on
<img
alt="SimpleLogin"
src="../images/icon-simplelogin.png"
style="height: 1.2em;"
/>
icon at the corner of your browser.
</p>
<p
v-if="isChrome"
class="alert alert-primary"
style="border-left: 5px #467fcf solid;"
>
Note: this icon maybe hidden under
<img
alt="Extension Menu"
src="../images/icon-puzzle.png"
style="height: 1.2em;"
/>
button.
</p>
</div>
</div>
</div>
</div>
<div class="arrow-up" v-if="step === 4">
<img alt="It is up here" src="../images/arrow-up.png" />
</div>
</div>
</template>
<script>
import SLStorage from "../popup/SLStorage";
import axios from "axios";
import { API_ROUTE } from "../popup/APIService";
import EventManager from "../popup/EventManager";
export default {
data() {
return {
step: 1,
isChrome:
/Chrome/.test(navigator.userAgent) &&
/Google Inc/.test(navigator.vendor),
isAuthenticated: false,
userName: "",
url: {
createNewAccount: "",
login: "",
},
};
},
async mounted() {
// get URL for buttons
const apiUrl = await SLStorage.get(SLStorage.SETTINGS.API_URL);
this.url.createNewAccount = `${apiUrl}/auth/register?next=%2Fdashboard%2Fsetup_done`;
this.url.login = `${apiUrl}/dashboard/setup_done`;
//Show first step while the request is being executed
setTimeout(async () => {
if (await this.tryGetUserInfo()) {
this.step = 3;
}
}, 1);
const updateStep = () => {
this.step = window.location.href.match(/#step3/) ? 3 : 1;
};
window.addEventListener("hashchange", updateStep, false);
updateStep();
},
methods: {
toStep(i) {
this.step = i;
},
nextStep() {
this.step = this.step + 1;
},
async tryGetUserInfo() {
const apiUrl = await SLStorage.get(SLStorage.SETTINGS.API_URL);
try {
// check api key
const response = await axios.get(
apiUrl + API_ROUTE.GET_USER_INFO.path,
{ headers: { Authentication: this.apiKey } }
);
this.userName = response.data.name || response.data.email;
this.isAuthenticated = true;
await SLStorage.set(SLStorage.SETTINGS.API_KEY, this.apiKey);
EventManager.broadcast(EventManager.EVENT.SETTINGS_CHANGED);
return true;
} catch (err) {
return false;
}
},
},
};
</script>
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SimpleLogin Extension</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="app"></div>
<script src="index.js"></script>
</body>
</html>
-12
View File
@@ -1,12 +0,0 @@
import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
import Onboarding from "./Onboarding";
import "./styles.scss";
Vue.use(BootstrapVue);
/* eslint-disable no-new */
new Vue({
el: "#app",
render: (h) => h(Onboarding),
});
-67
View File
@@ -1,67 +0,0 @@
@import '../popup/App-color.scss';
@import '../popup/App-scrollbar.scss';
@import '~bootstrap/scss/bootstrap.scss';
@import '~bootstrap-vue/src/index.scss';
.app {
& > .row {
width: 100vw;
}
.screen-left {
background: rgb(238,48,124);
background: linear-gradient(144deg, rgba(238,48,124,1) 0%, rgba(170,41,144,1) 100%);
height: 100vh;
}
.right-content {
position: absolute;
top: 50%;
left: 2em;
transform: translate(0, -50%);
max-height: 100vh;
overflow-y: auto;
h5 {
font-weight: bold;
}
.header {
.sl-logo {
width: 200px;
}
margin-bottom: 3em;
}
}
.left-content {
position: absolute;
top: 50%;
right: 2em;
transform: translate(0, -50%);
text-align: right;
p {
font-size: 1.2em;
color: white;
opacity: 0.5;
cursor: pointer;
}
p.active {
opacity: 1;
font-weight: bold;
}
}
.arrow-up {
position: fixed;
right: 2em;
top: 2em;
opacity: 0.7;
img {
width: 150px;
}
}
}
-2
View File
@@ -17,7 +17,6 @@ const config = {
entry: {
'background': './background/index.js',
'popup/popup': './popup/popup.js',
'onboarding/index': './onboarding/index.js',
},
output: {
path: __dirname + '/dist',
@@ -88,7 +87,6 @@ const config = {
{ from: 'images', to: 'images' },
{ from: 'content_script', to: 'content_script' },
{ from: 'popup/popup.html', to: 'popup/popup.html', transform: transformHtml },
{ from: 'onboarding/index.html', to: 'onboarding/index.html' },
{
from: 'manifest.json',
to: 'manifest.json',