Files

365 lines
9.6 KiB
JavaScript

/**
* @fileoverview I2P Privacy Manager
* Handles Firefox privacy settings and data cleanup for I2P container tabs
*/
const PRIVACY_CONFIG = {
TITLE_PREFACE: chrome.i18n.getMessage("titlePreface"),
SNOWFLAKE_ID: "{b11bea1f-a888-4332-8d8a-cec2be7d24b9}",
WINDOWS: {
OS: "win",
},
BROWSING_DATA: {
SINCE: "forever",
TYPES: ["downloads", "passwords", "formData", "localStorage", "history"],
},
};
/**
* Privacy Manager for handling browser privacy settings
*/
class PrivacyManager {
/**
* Set browser privacy settings
* @param {Object} settings Privacy setting configuration
* @return {Promise<boolean>}
*/
static async setSetting(setting, value) {
try {
const result = await setting.set({ value });
console.info(`Privacy setting updated : ${value}`);
return true;
} catch (error) {
console.error("Privacy setting failed:", error);
return false;
}
}
/**
* Configure hyperlink auditing
* @param {boolean} enabled Whether to enable auditing
*/
static async configureHyperlinkAuditing(enabled = false) {
await this.setSetting(
browser.privacy.websites.hyperlinkAuditingEnabled,
enabled
);
}
/**
* Configure first party isolation
* @param {boolean} enabled Whether to enable isolation
*/
static async configureFirstPartyIsolation(enabled = true) {
await this.setSetting(browser.privacy.websites.firstPartyIsolate, enabled);
}
/**
* Configure cookie behavior
* @param {boolean} allowThirdParty Whether to allow third party cookies
*/
static async configureCookies(allowThirdParty = false) {
try {
const cookieConfig = await browser.privacy.websites.cookieConfig.get({});
await browser.privacy.websites.cookieConfig.set({
value: {
behavior: allowThirdParty ? "allow_all" : "reject_third_party",
nonPersistentCookies: cookieConfig.value.nonPersistentCookies,
},
});
} catch (error) {
console.error("Cookie configuration failed:", error);
}
}
/**
* Configure referrer policy
* @param {boolean} enabled Whether to enable referrers
*/
static async configureReferrers(enabled = false) {
await this.setSetting(browser.privacy.websites.referrersEnabled, enabled);
}
/**
* Configure fingerprinting resistance
* @param {boolean} enabled Whether to enable resistance
*/
static async configureFingerprinting(enabled = true) {
await this.setSetting(
browser.privacy.websites.resistFingerprinting,
enabled
);
}
/**
* Configure tracking protection
* @param {boolean} enabled Whether to enable protection
*/
static async configureTrackingProtection(enabled = true) {
await this.setSetting(
browser.privacy.websites.trackingProtectionMode,
enabled ? "always" : "never"
);
}
/**
* Configure DRM content
* @param {boolean} enabled Whether to enable DRM
*/
static async configureDRM(enabled = false) {
const platformInfo = await browser.runtime.getPlatformInfo();
if (platformInfo.os === PRIVACY_CONFIG.WINDOWS.OS) {
await this.setSetting(
browser.privacy.websites.protectedContentEnabled,
enabled
);
}
}
/**
* Configure WebRTC
* @param {boolean} enabled Whether to enable WebRTC
*/
static async configureWebRTC(enabled = false) {
try {
const snowflake = await browser.management.get(
PRIVACY_CONFIG.SNOWFLAKE_ID
);
console.info("Snowflake detected, preserving WebRTC");
return;
} catch {
await this.setSetting(
browser.privacy.network.peerConnectionEnabled,
enabled
);
await this.setSetting(
chrome.privacy.network.webRTCIPHandlingPolicy,
enabled ? "disable_non_proxied_udp" : "default"
);
}
}
/**
* Configure password saving
* @param {boolean} enabled Whether to enable password saving
*/
static async configurePasswordSaving(enabled = false) {
await this.setSetting(
browser.privacy.services.passwordSavingEnabled,
enabled
);
}
/**
* Apply recommended privacy settings
*/
static async applyRecommendedSettings() {
await Promise.all([
this.configureHyperlinkAuditing(false),
this.configureFirstPartyIsolation(true),
this.configureCookies(false),
this.configureFingerprinting(true),
this.configureTrackingProtection(true),
this.configureDRM(false),
this.configureWebRTC(false),
this.configurePasswordSaving(false),
]);
}
/**
* Reset all privacy settings to defaults
*/
static async resetAllSettings() {
await Promise.all([
browser.privacy.websites.hyperlinkAuditingEnabled.clear(),
browser.privacy.websites.firstPartyIsolate.clear(),
browser.privacy.websites.cookieConfig.clear(),
browser.privacy.websites.referrersEnabled.clear(),
browser.privacy.websites.resistFingerprinting.clear(),
browser.privacy.websites.trackingProtectionMode.clear(),
browser.privacy.websites.protectedContentEnabled.clear(),
browser.privacy.network.peerConnectionEnabled.clear(),
browser.privacy.services.passwordSavingEnabled.clear(),
]);
}
}
/**
* Data Cleanup Manager for handling browsing data
*/
class DataCleanupManager {
/**
* Clean browsing data for I2P domains
* @param {Object} options Cleanup options
*/
static async cleanBrowsingData(options = {}) {
const since = this.calculateCleanupTime(
options.since || PRIVACY_CONFIG.BROWSING_DATA.SINCE
);
try {
const i2pHistory = await browser.history.search({
text: "i2p",
startTime: 0,
});
for (const item of i2pHistory) {
if (this.isI2PUrl(item.url)) {
await this.cleanupForDomain(item.url, since);
}
}
await this.notifyCleanup();
} catch (error) {
console.error("Data cleanup failed:", error);
}
}
/**
* Calculate cleanup timestamp
* @param {string} timeframe Cleanup timeframe
* @returns {number} Timestamp
*/
static calculateCleanupTime(timeframe) {
const times = {
hour: () => 1000 * 60 * 60,
day: () => 1000 * 60 * 60 * 24,
week: () => 1000 * 60 * 60 * 24 * 7,
forever: () => 0,
};
return timeframe === "forever"
? 0
: Date.now() - (times[timeframe] || times.forever)();
}
/**
* Clean up data for specific domain
* @param {string} url Domain URL
* @param {number} since Timestamp
*/
static async cleanupForDomain(url, since) {
const hostname = this.extractI2PHostname(url);
await Promise.all([
browser.history.deleteUrl({ url }),
browser.browsingData.removeCache({}),
browser.browsingData.removePasswords({ hostnames: [hostname], since }),
browser.browsingData.removeDownloads({ hostnames: [hostname], since }),
browser.browsingData.removeFormData({ hostnames: [hostname], since }),
browser.browsingData.removeLocalStorage({ hostnames: [hostname], since }),
]);
await this.cleanupContainerCookies(url);
}
/**
* Clean up container cookies
* @param {string} url Domain URL
*/
static async cleanupContainerCookies(url) {
const containers = await browser.contextualIdentities.query({
name: PRIVACY_CONFIG.TITLE_PREFACE,
});
for (const container of containers) {
const cookies = await browser.cookies.getAll({
firstPartyDomain: null,
storeId: container.cookieStoreId,
});
for (const cookie of cookies) {
await browser.cookies.remove({
firstPartyDomain: cookie.firstPartyDomain,
name: cookie.name,
url: url,
});
}
}
}
/**
* Extract I2P hostname from URL
* @param {string} url URL to parse
* @returns {string} I2P hostname
*/
static extractI2PHostname(url) {
try {
const urlObj = new URL(url);
if (urlObj.host.endsWith(".i2p")) {
return urlObj.host;
}
if (url.includes(".i2p")) {
const parts = url.split("=");
for (const part of parts) {
const items = part.split("%");
for (const item of items) {
if (item.includes(".i2p")) {
return item.replace("3D", "");
}
}
}
}
return url.split("/")[2] || url.split("/")[0];
} catch (error) {
console.error("Hostname extraction failed:", error);
return "";
}
}
/**
* Check if URL is I2P
* @param {string} url URL to check
* @returns {boolean}
*/
static isI2PUrl(url) {
const hostname = this.extractI2PHostname(url);
return hostname.split(":")[0].endsWith(".i2p");
}
/**
* Send cleanup notification
*/
static async notifyCleanup() {
await browser.notifications.create({
type: "basic",
title: "Removed browsing data",
message: "Cleaned I2P browsing data and history",
});
}
}
// Initialize privacy settings
PrivacyManager.applyRecommendedSettings();
// Listen for uninstall
browser.management.onUninstalled.addListener(async (info) => {
const selfInfo = await browser.management.getSelf();
if (info.name === selfInfo.name) {
await PrivacyManager.resetAllSettings();
}
});
// Listen for messages
browser.runtime.onMessage.addListener(async (message) => {
switch (message.type) {
case "cleanupData":
await DataCleanupManager.cleanBrowsingData(message.options);
break;
case "updatePrivacy":
await PrivacyManager.applyRecommendedSettings();
break;
}
});
// Export for testing
if (typeof module !== "undefined" && module.exports) {
module.exports = {
PrivacyManager,
DataCleanupManager,
PRIVACY_CONFIG,
};
}