Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • weblate/metager-webextension
  • open-source/metager-webextension
2 results
Show changes
Commits on Source (7)
# v1.2
* Add hint to show if settings are applied in inkognito mode
* Fix: Interface on permission updates
* Fix: NetRequestRules not getting applied to Chrome startpages
# v0.1.0.3
* Fix: Broken link to help page for homomorphic encryption
......
......@@ -49,5 +49,8 @@
},
"settings_anonymous_tokens_setup_key": {
"message": "Setup your MetaGer key"
},
"settings_inkognito": {
"message": "Your MetaGer settings will not be applied in inkognito tabs. If you want to change this you can allow this extension to run in in those."
}
}
\ No newline at end of file
......@@ -35,8 +35,8 @@
}
},
"release_notes": {
"de": "Fix: Schlüssel wurde beim Abmelden nicht richtig entfernt\nFix: Token-Auffüllung, wenn Erweiterungsspeicher beschädigt war\nFix: Schlüssel bei Installation richtig importieren",
"en-US": "Fix: key wasn't properly removed on logout\nFix: Token refill when extension storage was corrupted\nFix: properly import key on installation"
"de": "",
"en-US": ""
}
},
"is_disabled": false,
......
......@@ -21,7 +21,18 @@ export class SettingsManager {
store_settings = true;
settings = {};
constructor() { }
urls = [];
origins = [];
hosts = [];
constructor() {
browser.runtime.getManifest().host_permissions.forEach((value, index) => {
this.urls.push(value);
let url = new URL(value);
this.hosts.push(url.host);
this.origins.push(url.origin);
});
}
async init() {
if (this.initialized != null) return this.initialized;
......@@ -85,21 +96,11 @@ export class SettingsManager {
if (changeInfo.removed) return; // Do not handle removed cookies
if (!this.store_settings) return; // Do not handle cookies if we're currently not storing settings in extension
if (this._ignored_settings.includes(changeInfo.cookie.name)) return; // Do not handle ignored settings
let protocol = changeInfo.cookie.secure ? "https" : "http";
return browser.cookies.remove({
name: changeInfo.cookie.name,
url: `https://${changeInfo.cookie.domain}${changeInfo.cookie.path}`
});
}
async handlePermissionsChange(permissions) {
await this.init();
this.initialized = false;
return this.init().then(() => {
if (this.store_settings) {
return this._enable();
} else {
return this._disable();
}
url: `${protocol}://${changeInfo.cookie.domain}${changeInfo.cookie.path}`
});
}
......@@ -215,14 +216,12 @@ export class SettingsManager {
});
}
let rules = [];
// Add Rule for web extension version
rules.push({
id: 6,
condition: {
requestDomains: [
"metager.de",
"metager.org"
],
requestDomains: this.hosts,
resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
},
action: {
......@@ -238,10 +237,7 @@ export class SettingsManager {
rules.push({
id: 1,
condition: {
requestDomains: [
"metager.de",
"metager.org"
],
requestDomains: this.hosts,
resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
},
action: {
......@@ -260,25 +256,23 @@ export class SettingsManager {
this.store_settings = true;
if (!browser.cookies) return;
// First fetch all cookies from metager.org
await browser.cookies.getAll({ url: "https://metager.org/" }).then(async cookies => {
let cookie_promises = [];
for (let cookie of cookies) {
if (this._ignored_settings.includes(cookie.name)) continue;
this.settings[cookie.name] = cookie.value;
cookie_promises.push(browser.cookies.remove({ name: cookie.name, url: "https://metager.org/" }));
}
return Promise.all(cookie_promises);
}).catch(error => console.error(error));
await browser.cookies.getAll({ url: "https://metager.de/" }).then(async cookies => {
let cookie_promises = [];
for (let cookie of cookies) {
if (this._ignored_settings.includes(cookie.name)) continue;
this.settings[cookie.name] = cookie.value;
cookie_promises.push(browser.cookies.remove({ name: cookie.name, url: "https://metager.de/" }));
}
return Promise.all(cookie_promises);
}).catch(error => console.error(error));
return this.sync();
// Fetch cookies from all origins
let cookie_promises = [];
this.origins.forEach(origin => {
let origin_url = new URL(origin);
cookie_promises.push(browser.cookies.getAll({ url: origin }).then(cookies => {
let cookie_delete_promises = [];
for (let cookie of cookies) {
if (this._ignored_settings.includes(cookie.name)) continue;
this.settings[cookie.name] = cookie.value;
cookie_delete_promises.push(browser.cookies.remove({ name: cookie.name, url: origin }));
}
return Promise.all(cookie_delete_promises);
}).catch(error => console.error(error)));
});
return Promise.all(cookie_promises).then(() => this.sync());
}
async _disable() {
......@@ -289,21 +283,18 @@ export class SettingsManager {
let expiration = new Date();
expiration.setFullYear(expiration.getFullYear() + 1);
for (let setting_key in this.settings) {
create_cookies_promises.push(browser.cookies.set({
url: "https://metager.de/",
name: setting_key,
value: this.settings[setting_key],
expirationDate: Math.round(expiration.getTime() / 1000),
secure: true
}).catch(error => console.error(error)));
create_cookies_promises.push(browser.cookies.set({
url: "https://metager.org/",
name: setting_key,
value: this.settings[setting_key],
expirationDate: Math.round(expiration.getTime() / 1000),
secure: true
}).catch(error => console.error(error)));
this.origins.forEach(origin => {
let origin_url = new URL(origin);
create_cookies_promises.push(browser.cookies.set({
url: origin,
name: setting_key,
value: this.settings[setting_key],
expirationDate: Math.round(expiration.getTime() / 1000),
secure: origin_url.protocol != "http:"
}).catch(error => console.error(error)));
});
}
return Promise.all(create_cookies_promises).then(async () => this.updateSettingRule());
}
}
......
......@@ -32,6 +32,7 @@ export class TokenManager {
};
_handled_cookies = ["key", "cost", "tokens"]
_permissions_granted = false;
_initialized = null;
_anonymous_tokens_enabled = false;
......@@ -49,7 +50,20 @@ export class TokenManager {
_recent_costs = []; // The latest cost for the last few MetaGer searches
_recent_costs_number = 3; // How many recent costs to account for in calculation
_urls = [];
_origins = [];
_hosts = [];
constructor() {
browser.runtime.getManifest().host_permissions.forEach((value, index) => {
this._urls.push(value);
let url = new URL(value);
this._hosts.push(url.host);
this._origins.push(url.origin);
});
if (process.env.NODE_ENV == "development") {
this._api_base = "http://localhost:8080/keys/api/json";
}
this.init();
}
......@@ -63,8 +77,8 @@ export class TokenManager {
this._initialized = browser.storage.sync
.get(settings)
.then(async (storage) => {
let permissions_granted = await this._checkPermissions();
if (permissions_granted == false) {
this._permissions_granted = await this._checkPermissions();
if (this._permissions_granted == false) {
this._anonymous_tokens_enabled = false;
} else if (storage[this._storage_keys.anonymous_tokens_enabled] == null) {
this._anonymous_tokens_enabled = true;
......@@ -91,9 +105,9 @@ export class TokenManager {
this._recent_costs = storage[this._storage_keys.tokens_recent_costs];
this._key_charge = storage[this._storage_keys.key_charge];
this._key_charge_check = storage[this._storage_keys.key_charge_check];
setInterval(this.refill.bind(this), 15000);
return this.refill();
}).then(() => this._updateNetRequestRules());
return this._initialized;
}
async handleStorageChange(changes) {
......@@ -183,58 +197,49 @@ export class TokenManager {
return;
}
/**
* When Extension permission change try to load key from cookie in browser
* and reinitialize Tokenmanager
*
* @param {*} permissions
* @returns
*/
async handlePermissionsChange(permissions) {
await this.init();
this._initialized = null;
return this._checkPermissions().then(async granted => {
if (granted) {
return this._load_key_from_browser().then(() => this.init());
} else {
return this.init();
}
});
}
async handleCookieChange(changeInfo) {
await this.init();
if (changeInfo.removed) return; // Do not handle removed cookies
if (!this._handled_cookies.includes(changeInfo.cookie.name)) return; // Do not handle unspecified cookies
let protocol = changeInfo.cookie.secure ? "https" : "http";
return browser.cookies.remove({
name: changeInfo.cookie.name,
url: `https://${changeInfo.cookie.domain}${changeInfo.cookie.path}`
url: `${protocol}://${changeInfo.cookie.domain}${changeInfo.cookie.path}`
});
}
/**
* Returns the current key charge
*/
async getKeyCharge() {
return this._fetch_key_charge();
}
async _load_key_from_browser() {
if (!this._permissions_granted) return;
// Consume a possible key cookie
await browser.cookies.get({ name: "key", url: "https://metager.org/" }).then(async key_cookie => {
if (key_cookie == null) {
return;
let key = "";
let cookie_promises = [];
for (let i = 0; i < this._origins.length; i++) {
let origin = this._origins[i];
let key_cookie = await browser.cookies.get({ name: "key", url: origin });
if (key_cookie != null) {
key = key_cookie.value;
cookie_promises.push(browser.cookies.remove({ name: "key", url: origin }));
}
return this.store_key(key_cookie.value).then(async () => {
return browser.cookies.remove({ name: "key", url: "https://metager.org/" });
});
}).catch(error => console.error(error));
await browser.cookies.get({ name: "key", url: "https://metager.de/" }).then(async key_cookie => {
if (key_cookie == null) {
return;
}
return Promise.all(cookie_promises).then(() => {
if (key.length > 0) {
return this.store_key(key);
}
return this.store_key(key_cookie.value).then(async () => {
return browser.cookies.remove({ name: "key", url: "https://metager.de/" });
});
}).catch(error => console.error(error));
}
async _fetch_key_charge() {
if (this._key == null) return 0;
return fetch("https://metager.de/keys/api/json/key/" + encodeURIComponent(this._key), {
return fetch(`${this._api_base}/key/` + encodeURIComponent(this._key), {
headers: {
"Cache-Control": "no-cache",
}
......@@ -258,10 +263,7 @@ export class TokenManager {
rules.push({
id: 2,
condition: {
requestDomains: [
"metager.de",
"metager.org"
],
requestDomains: this._hosts,
urlFilter: "/keys/*",
resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
},
......@@ -277,10 +279,7 @@ export class TokenManager {
rules.push({
id: 3,
condition: {
requestDomains: [
"metager.de",
"metager.org"
],
requestDomains: this._hosts,
urlFilter: "/meta/settings*",
resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
},
......@@ -299,10 +298,7 @@ export class TokenManager {
rules.push({
id: 4,
condition: {
requestDomains: [
"metager.de",
"metager.org"
],
requestDomains: this._hosts,
resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
},
action: {
......@@ -323,10 +319,7 @@ export class TokenManager {
rules.push({
id: 2,
condition: {
requestDomains: [
"metager.de",
"metager.org"
],
requestDomains: this._hosts,
resourceTypes: ["main_frame", "sub_frame", "xmlhttprequest"]
},
action: {
......@@ -476,10 +469,7 @@ export class TokenManager {
*/
async _checkPermissions() {
let permissionsToRequest = {
origins: [
"https://metager.org/*",
"https://metager.de/*",
]
origins: this._urls
};
return browser.permissions.contains(permissionsToRequest);
......@@ -506,7 +496,7 @@ export class TokenManager {
if (this._key_charge == 0) {
// If the key is empty there is no need to try to generate signed tokens on each request
// Instead we will regulary check if the key was charged
if ((this._key_charge_check + 15 * 60 * 1000) < (new Date()).getTime()) {
if ((this._key_charge_check + 1 * 60 * 1000) < (new Date()).getTime()) {
// Check key charge
return this._fetch_key_charge().then(async charge => {
let settings = {};
......
......@@ -8,6 +8,34 @@ if (typeof browser == "undefined") {
var browser = chrome;
}
urls = [];
origins = [];
hosts = [];
browser.runtime.getManifest().host_permissions.forEach((value, index) => {
urls.push(value);
let url = new URL(value);
hosts.push(url.host);
origins.push(url.origin);
});
/**
* Execute things when a new browser profile is created
*/
browser.runtime.onStartup.addListener(async () => {
// Initialize our settings managers
let init_promises = [settingsManager.init(), tokenManager.init()];
return Promise.all(init_promises).then(() => {
if (BROWSER != "CHROME") return;
/** Chrome currently does not apply NetRequestRules to startpages. But they will be applied after a reload */
browser.tabs.query({}).then(tabs => {
let reloads = [];
tabs.forEach(tab => reloads.push(browser.tabs.reload(tab.id)));
return Promise.all(reloads);
})
});
});
/**
* Handle messages between content scripts and background script:
......@@ -54,6 +82,17 @@ browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
});
return true;
}
if (request.type == "key") {
if (request.hasOwnProperty("action") && request.action == "getcharge") {
tokenManager.getKeyCharge().then(charge => {
sendResponse({ status: "ok", data: { charge: charge } });
}).catch(error => {
console.trace(error);
sendResponse({ status: "error" });
});
return true;
}
}
});
/**
......@@ -69,12 +108,12 @@ browser.storage.onChanged.addListener(async (changes, areaName) => {
tokenManager.handleStorageChange(changes);
});
browser.permissions.onAdded.addListener(async (permissions) => {
await settingsManager.handlePermissionsChange(permissions);
await tokenManager.handlePermissionsChange(permissions);
browser.runtime.reload();
return;
});
browser.permissions.onRemoved.addListener(async (permissions) => {
await settingsManager.handlePermissionsChange(permissions);
await tokenManager.handlePermissionsChange(permissions);
browser.runtime.reload();
return;
});
/**
......@@ -105,7 +144,7 @@ browser.webRequest.onBeforeSendHeaders.addListener(
return { requestHeaders: headers };
},
{
urls: ["https://metager.org/*", "https://metager.de/*"],
urls: origins.map(origin => origin + "/*"),
},
extraInfoSpecRequest
);
......@@ -116,12 +155,11 @@ if (BROWSER == "CHROME") {
}
browser.webRequest.onHeadersReceived.addListener(
(details) => {
settingsManager.handleResponseHeaders(details);
tokenManager.handleResponseHeaders(details);
},
{
urls: ["https://metager.org/*", "https://metager.de/*"],
urls: origins.map(origin => origin + "/*"),
},
extraInfoSpecResponse
);
......
......@@ -2,7 +2,7 @@
"author": "SUMA-EV",
"manifest_version": 3,
"name": "__MSG_searchProviderName__",
"version": "1.1",
"version": "1.2",
"description": "__MSG_extensionDescription__",
"default_locale": "en",
"browser_specific_settings": {
......@@ -83,9 +83,7 @@
{
"matches": [
"https://metager.org/keys/*",
"https://metager.org/*/keys/*",
"https://metager.de/keys/*",
"https://metager.de/*/keys/*"
"https://metager.org/*/keys/*"
],
"run_at": "document_idle",
"js": [
......
......@@ -53,6 +53,9 @@
</div>
</div>
</div>
<div class="setting" id="inkognito">
<p data-text="settings_inkognito"></p>
</div>
<script src="strings.js"></script>
<script src="index.js" async></script>
<script src="tokenstatus.js" async></script>
......
......@@ -6,8 +6,27 @@ if (typeof browser == "undefined") {
(async () => {
let store_settings = null;
let use_anonymous_tokens = null;
let permissions_granted = false;
let inkognito_allowed = false;
let checkbox_store_settings = document.querySelector("#store-settings-switch");
let checkbox_anonymous_tokens = document.querySelector("#anonymous-tokens-switch");
let inkognito_settings_container = document.querySelector("#inkognito");
let urls = [];
let origins = [];
let hosts = [];
browser.runtime.getManifest().host_permissions.forEach((value, index) => {
urls.push(value);
let url = new URL(value);
hosts.push(url.host);
origins.push(url.origin);
});
let permissions = [
"storage",
"cookies",
"webRequest",
"declarativeNetRequestWithHostAccess"
];
await initializeSettingsState();
......@@ -54,10 +73,14 @@ if (typeof browser == "undefined") {
* change
*/
browser.permissions.onAdded.addListener(async (permissions) => {
return initializeSettingsState();
permissions_granted = await verifyPermissions();
showSettingsState();
return;
});
browser.permissions.onRemoved.addListener(async (permissions) => {
return initializeSettingsState();
permissions_granted = await verifyPermissions();
showSettingsState();
return;
});
/**
......@@ -68,12 +91,9 @@ if (typeof browser == "undefined") {
*/
async function verifyPermissions(request = false) {
let permissionsToRequest = {
origins: [
"https://metager.org/*",
"https://metager.de/*",
]
origins: urls,
permissions: permissions
};
if (request) {
return browser.permissions.request(permissionsToRequest);
} else {
......@@ -83,32 +103,20 @@ if (typeof browser == "undefined") {
/**
* Loads the current settings from browser storage and
* cross checks those settings with the app permissions.
* If permissions are missing all features will be turned off
* regardless of the setting state and can be turned on again
* by the user
* verifies current permission state
*/
async function initializeSettingsState() {
// Update permissions state
permissions_granted = await verifyPermissions();
// Initialize stored settings
let synced_settings = await browser.storage.sync.get({
settings_store: true,
use_anonymous_tokens: true,
});
if (synced_settings.settings_store == true || synced_settings.use_anonymous_tokens == true) {
await verifyPermissions().then(granted => {
if (granted) {
store_settings = synced_settings.settings_store;
use_anonymous_tokens = synced_settings.use_anonymous_tokens;
} else {
store_settings = false;
use_anonymous_tokens = false;
}
});
} else {
store_settings = false;
use_anonymous_tokens = false;
}
store_settings = synced_settings.settings_store;
use_anonymous_tokens = synced_settings.use_anonymous_tokens;
inkognito_allowed = await browser.extension.isAllowedIncognitoAccess();
showSettingsState();
}
......@@ -118,8 +126,13 @@ if (typeof browser == "undefined") {
* which is stored in local variables.
*/
function showSettingsState() {
checkbox_store_settings.checked = store_settings;
checkbox_anonymous_tokens.checked = use_anonymous_tokens;
checkbox_store_settings.checked = store_settings && permissions_granted;
checkbox_anonymous_tokens.checked = use_anonymous_tokens && permissions_granted;
if (inkognito_allowed) {
inkognito_settings_container.style.display = "none";
} else {
inkognito_settings_container.style.display = null;
}
}
/**
......@@ -135,21 +148,18 @@ if (typeof browser == "undefined") {
let new_settings = {};
if (store_settings != null) new_settings["settings_store"] = store_settings;
if (use_anonymous_tokens != null) new_settings["use_anonymous_tokens"] = use_anonymous_tokens;
inkognito_allowed = await browser.extension.isAllowedIncognitoAccess();
if (store_settings == true || use_anonymous_tokens == true) {
return verifyPermissions(true).then(permission_granted => {
if (permission_granted) {
return verifyPermissions(true).then(granted => {
permissions_granted = granted;
if (permissions_granted) {
return browser.storage.sync.set(new_settings);
} else {
checkbox_store_settings.checked = false;
checkbox_anonymous_tokens.checked = false;
return;
}
})
} else {
return browser.storage.sync.set(new_settings);
}
}
})();
......@@ -2,10 +2,27 @@ let permissions_granted = null;
let key = null;
let charge = 0;
let anonymous_token = 0;
let urls = [];
let origins = [];
let hosts = [];
let permissions = [
"storage",
"cookies",
"webRequest",
"declarativeNetRequestWithHostAccess"
];
if (typeof browser == "undefined") {
var browser = chrome;
}
(() => {
browser.runtime.getManifest().host_permissions.forEach((value, index) => {
urls.push(value);
let url = new URL(value);
hosts.push(url.host);
origins.push(url.origin);
});
initialize();
})();
......@@ -20,7 +37,7 @@ browser.storage.onChanged.addListener(async (changes, areaName) => {
key = new_value;
await fetch_key_charge().then(key_charge => {
charge = key_charge;
}).catch(error => { }); // The request might fail when
}).catch(error => { }); // The request might fail when permissions are missing
update = true;
}
if (changes.hasOwnProperty("anonymous_tokens")) {
......@@ -54,20 +71,12 @@ browser.permissions.onRemoved.addListener(async (permissions) => {
* @returns {Promise<number>}
*/
async function fetch_key_charge() {
if (key == null) return 0;
return fetch("https://metager.de/keys/api/json/key/" + encodeURIComponent(key), {
headers: {
"Cache-Control": "no-cache",
}
}).then(async response => {
if (response.ok) {
return response.json();
return browser.runtime.sendMessage({ type: "key", action: "getcharge" }).then(response => {
if (response && response.status == "ok") {
return response.data.charge;
} else {
throw new Error("Couldn't fetch key status");
return 0;
}
}).then(async response => {
charge = response.charge;
return charge;
});
}
......@@ -81,10 +90,8 @@ async function fetch_key_charge() {
*/
async function initialize() {
let permissionsToRequest = {
origins: [
"https://metager.org/*",
"https://metager.de/*",
]
origins: urls,
permissions: permissions
};
// Initialize state
return browser.permissions.contains(permissionsToRequest)
......
......@@ -2,7 +2,7 @@ const CopyPlugin = require("copy-webpack-plugin");
const path = require("path");
const { ProvidePlugin, DefinePlugin } = require("webpack");
module.exports = {
module.exports = (env, argv) => ({
resolve: {
fallback: {
assert: false,
......@@ -39,7 +39,7 @@ module.exports = {
from: __dirname + "/build/manifest.json",
to: __dirname + "/manifest.json",
transform(content, path) {
return buildManifest(content);
return buildManifest(content, argv.mode);
}
}]
}),
......@@ -47,13 +47,33 @@ module.exports = {
BROWSER: JSON.stringify(process.env.BROWSER)
}),
]
};
});
function buildManifest(content) {
function buildManifest(content, mode = "production") {
let manifest = JSON.parse(content);
let browser_env = process.env.BROWSER;
if (browser_env) {
if (mode == "development") {
// Add access to localhost for development environments
manifest.content_scripts[0].matches.push("http://localhost:8080/*");
manifest.content_scripts[1].matches.push(
'http://localhost:8080/meta/settings',
'http://localhost:8080/*/meta/settings',
'http://localhost:8080/meta/settings?*',
'http://localhost:8080/*/meta/settings?*'
);
manifest.content_scripts[2].matches.push(
'http://localhost:8080/meta/meta.ger3?*',
'http://localhost:8080/*/meta/meta.ger3?*'
);
manifest.content_scripts[3].matches.push(
'http://localhost:8080/keys/*',
'http://localhost:8080/*/keys/*'
);
manifest.host_permissions.push("http://localhost/*", "http://localhost:8080/*");
}
if (browser_env == "CHROME") {
// Chrome does not support background_scripts
// We need to convert to service_worker
......