1067 lines
27 KiB
JavaScript
1067 lines
27 KiB
JavaScript
/**
|
|
* @fileoverview I2P Container Tab Manager
|
|
* Handles container isolation, header scrubbing and tab management
|
|
*/
|
|
|
|
// Configuration constants
|
|
const CONTAINER_CONFIG = {
|
|
MESSAGES: {
|
|
TITLE: chrome.i18n.getMessage("titlePreface"),
|
|
WEB: chrome.i18n.getMessage("webPreface"),
|
|
ROUTER: chrome.i18n.getMessage("routerPreface"),
|
|
MAIL: chrome.i18n.getMessage("mailPreface"),
|
|
TORRENT: chrome.i18n.getMessage("torrentPreface"),
|
|
TUNNEL: chrome.i18n.getMessage("i2ptunnelPreface"),
|
|
IRC: chrome.i18n.getMessage("ircPreface"),
|
|
EXTENSION: chrome.i18n.getMessage("extensionPreface"),
|
|
MUWIRE: chrome.i18n.getMessage("muwirePreface"),
|
|
BOTE: chrome.i18n.getMessage("botePreface"),
|
|
BLOG: chrome.i18n.getMessage("blogPreface"),
|
|
BLOG_PRIVATE: chrome.i18n.getMessage("blogPrefacePrivate"),
|
|
TOR: chrome.i18n.getMessage("torPreface"),
|
|
TOR_PRIVATE: chrome.i18n.getMessage("torPrefacePrivate"),
|
|
},
|
|
|
|
HEADER_CONFIG: {
|
|
USER_AGENT: "MYOB/6.66 (AN/ON)",
|
|
TITLE_PREFIX: "myob",
|
|
},
|
|
|
|
URLS: {
|
|
BASE: "http://proxy.i2p",
|
|
LOCAL: {
|
|
IRC: "http://127.0.0.1:7669",
|
|
TOR: "http://127.0.0.1:7695",
|
|
BLOG: "http://127.0.0.1:8084",
|
|
},
|
|
POPUPS: {
|
|
SECURITY: "security.html",
|
|
LOCATION: "location.html",
|
|
TORRENT: "torrent.html",
|
|
},
|
|
ICONS: {
|
|
I2P: "icons/i2plogo.png",
|
|
INFOTOOPIE: "icons/infotoopie.png",
|
|
INFOTOOPIES: "icons/infotoopies.png",
|
|
INFOTOOPIEBT: "icons/infotoopiesbt.png",
|
|
},
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Header Manager for privacy protection
|
|
*/
|
|
class HeaderManager {
|
|
/**
|
|
* Get container context for tab
|
|
* @param {Object} tabInfo Tab information
|
|
* @return {Promise<Object>} Container context or null for default contexts
|
|
*/
|
|
|
|
static async getContext(tabInfo) {
|
|
try {
|
|
if (!tabInfo || !tabInfo.cookieStoreId) {
|
|
return null; // Return null for missing context info
|
|
}
|
|
|
|
// Handle default and private contexts
|
|
if (
|
|
tabInfo.cookieStoreId === "firefox-default" ||
|
|
tabInfo.cookieStoreId === "firefox-private"
|
|
) {
|
|
return null; // Return null for default/private contexts
|
|
}
|
|
|
|
// Only lookup context for container tabs
|
|
const context = await browser.contextualIdentities.get(
|
|
tabInfo.cookieStoreId
|
|
);
|
|
return context;
|
|
} catch (error) {
|
|
console.debug("Tab is using default context"); // More descriptive message
|
|
return null; // Return null on error
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process headers for request
|
|
* @param {Object} requestDetails Request details
|
|
* @return {Promise<Object>} Modified request
|
|
*/
|
|
static async processHeaders(requestDetails) {
|
|
try {
|
|
if (
|
|
!requestDetails ||
|
|
!requestDetails.tabId ||
|
|
requestDetails.tabId <= 0
|
|
) {
|
|
return HeaderManager.scrubHeaders(requestDetails);
|
|
}
|
|
|
|
const tab = await UITabManager.getTab(requestDetails.tabId);
|
|
if (!tab) {
|
|
return HeaderManager.scrubHeaders(requestDetails);
|
|
}
|
|
|
|
// Always scrub headers regardless of container context
|
|
return HeaderManager.scrubHeaders(requestDetails);
|
|
} catch (error) {
|
|
console.error("Header processing failed:", error);
|
|
// Fail safe: still scrub headers even if context lookup fails
|
|
return HeaderManager.scrubHeaders(requestDetails);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scrub privacy-sensitive headers
|
|
* @param {Object} requestDetails Request details
|
|
* @return {Object} Modified headers
|
|
*/
|
|
static scrubHeaders(requestDetails) {
|
|
if (!requestDetails || !requestDetails.requestHeaders) {
|
|
return { requestHeaders: [] };
|
|
}
|
|
|
|
const headers = requestDetails.requestHeaders;
|
|
headers.forEach((header) => {
|
|
const headerName = header.name.toLowerCase();
|
|
|
|
if (headerName === "user-agent") {
|
|
header.value = CONTAINER_CONFIG.HEADER_CONFIG.USER_AGENT;
|
|
}
|
|
|
|
if (headerName === "referer") {
|
|
header.value = "";
|
|
}
|
|
});
|
|
|
|
return { requestHeaders: headers };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tab Manager for handling browser tabs
|
|
*/
|
|
class UITabManager {
|
|
/**
|
|
* Get tab information
|
|
* @param {number} tabId Tab ID
|
|
* @returns {Promise<Object>} Tab info
|
|
*/
|
|
static async getTab(tabId) {
|
|
try {
|
|
if (!tabId || tabId <= 0) {
|
|
return undefined;
|
|
}
|
|
return await browser.tabs.get(tabId);
|
|
} catch (error) {
|
|
console.error("Tab lookup failed:", error);
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update tab URL
|
|
* @param {Object} tab Browser tab
|
|
* @param {string} url New URL
|
|
*/
|
|
static async updateTab(tab, url) {
|
|
try {
|
|
if (!tab || !tab.id || !url) {
|
|
return;
|
|
}
|
|
await browser.tabs.update(tab.id, { url });
|
|
} catch (error) {
|
|
console.error("Tab update failed:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process tab for container
|
|
* @param {Object} tab Browser tab
|
|
* @param {string} containerName Container name
|
|
* @param {boolean} pin Whether to pin tab
|
|
*/
|
|
static async processContainerTab(tab, containerName, pin) {
|
|
try {
|
|
if (!tab || !containerName) {
|
|
return;
|
|
}
|
|
return await ContainerManager.forceIntoContainer(tab, containerName, pin);
|
|
} catch (error) {
|
|
console.error("Container tab processing failed:", error);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Container Manager for handling I2P container isolation
|
|
*/
|
|
/**
|
|
* Container Manager for handling I2P container isolation
|
|
*/
|
|
class ContainerManager {
|
|
/**
|
|
* Force tab into container
|
|
* @param {Object} tab Tab to isolate
|
|
* @param {string} contextId Container context ID
|
|
* @param {boolean} pin Whether to pin tab
|
|
* @returns {Promise<Object>} Containerized tab
|
|
*/
|
|
static async forceIntoContainer(tab, contextId, pin = true) {
|
|
try {
|
|
if (!tab || !contextId) {
|
|
throw new Error("Invalid tab or context");
|
|
}
|
|
|
|
const context = await browser.contextualIdentities.query({
|
|
name: contextId,
|
|
});
|
|
|
|
if (!context || !context[0]) {
|
|
throw new Error(`Container not found: ${contextId}`);
|
|
}
|
|
|
|
// Important: Check if tab is already in ANY container
|
|
if (
|
|
tab.cookieStoreId !== "firefox-default" &&
|
|
tab.cookieStoreId !== "firefox-private"
|
|
) {
|
|
return tab; // Tab is already containerized
|
|
}
|
|
|
|
const newURL = URLManager.getContainerURL(contextId, tab.url);
|
|
const newTab = await this.createContainerTab(context[0], newURL, pin);
|
|
await this.cleanupTabs(tab, newTab, context[0], pin);
|
|
|
|
return newTab;
|
|
} catch (error) {
|
|
console.error("Container isolation failed:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if tab is in container
|
|
* @param {Object} tab Browser tab
|
|
* @returns {boolean} Whether tab is in container
|
|
*/
|
|
static isInContainer(tab) {
|
|
return (
|
|
tab.cookieStoreId !== "firefox-default" &&
|
|
tab.cookieStoreId !== "firefox-private"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create new container tab
|
|
* @private
|
|
* @param {Object} context Container context
|
|
* @param {string} url Tab URL
|
|
* @param {boolean} pin Whether to pin tab
|
|
* @returns {Promise<Object>} Created tab
|
|
*/
|
|
static async createContainerTab(context, url, pin) {
|
|
return await browser.tabs.create({
|
|
active: true,
|
|
cookieStoreId: context.cookieStoreId,
|
|
url: url,
|
|
pinned: pin,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Clean up tabs after container move
|
|
* @private
|
|
* @param {Object} oldTab Original tab
|
|
* @param {Object} newTab New container tab
|
|
* @param {Object} context Container context
|
|
* @param {boolean} pin Whether to pin tab
|
|
*/
|
|
static async cleanupTabs(oldTab, newTab, context, pin) {
|
|
// Only remove the old tab if it's not in a container
|
|
if (!this.isInContainer(oldTab)) {
|
|
await browser.tabs.remove(oldTab.id);
|
|
}
|
|
|
|
if (pin) {
|
|
await browser.tabs.move(newTab.id, { index: 0 });
|
|
|
|
// Only clean up other tabs if explicitly requested
|
|
const tabs = await browser.tabs.query({
|
|
cookieStoreId: context.cookieStoreId,
|
|
});
|
|
|
|
for (const tab of tabs) {
|
|
if (tab.id !== newTab.id && !tab.pinned) {
|
|
await browser.tabs.remove(tab.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
await PageActionManager.setupSecurityPopup(newTab.id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* URL Manager for handling container URLs
|
|
*/
|
|
class URLManager {
|
|
/**
|
|
* Get URL for container context
|
|
* @param {string} contextId Container context ID
|
|
* @param {string} url Original URL
|
|
* @returns {string} Container URL
|
|
*/
|
|
static getContainerURL(contextId, url) {
|
|
if (!url.startsWith("moz-extension://")) {
|
|
return url;
|
|
}
|
|
|
|
const routerUrl = this.getRouterBaseURL();
|
|
|
|
const urlMap = {
|
|
[CONTAINER_CONFIG.MESSAGES.TITLE]: CONTAINER_CONFIG.URLS.BASE,
|
|
[CONTAINER_CONFIG.MESSAGES.ROUTER]: `${routerUrl}console`,
|
|
[CONTAINER_CONFIG.MESSAGES.TUNNEL]: `${routerUrl}i2ptunnel`,
|
|
[CONTAINER_CONFIG.MESSAGES.MUWIRE]: `${routerUrl}MuWire`,
|
|
[CONTAINER_CONFIG.MESSAGES.BOTE]: `${routerUrl}i2pbote`,
|
|
[CONTAINER_CONFIG.MESSAGES.MAIL]: `${routerUrl}webmail`,
|
|
[CONTAINER_CONFIG.MESSAGES.IRC]: CONTAINER_CONFIG.URLS.LOCAL.IRC,
|
|
[CONTAINER_CONFIG.MESSAGES.TOR]: CONTAINER_CONFIG.URLS.LOCAL.TOR,
|
|
[CONTAINER_CONFIG.MESSAGES.BLOG]: CONTAINER_CONFIG.URLS.LOCAL.BLOG,
|
|
};
|
|
|
|
return urlMap[contextId] || CONTAINER_CONFIG.URLS.BASE;
|
|
}
|
|
|
|
/**
|
|
* Get router base URL
|
|
* @private
|
|
* @returns {string} Router URL
|
|
*/
|
|
static getRouterBaseURL() {
|
|
const host = control_host();
|
|
const port = control_port();
|
|
return `http://${host}:${port}/`;
|
|
}
|
|
|
|
/**
|
|
* Fix torrent URL
|
|
* @param {string} url Original URL
|
|
* @returns {string} Fixed URL
|
|
*/
|
|
static fixTorrentURL(url) {
|
|
if (!url.endsWith("xhr1.html")) {
|
|
return url;
|
|
}
|
|
|
|
const urlParts = url.split("/");
|
|
const hostname = urlParts[2];
|
|
const protocol = url.substr(0, url.indexOf("://") + 3);
|
|
|
|
return `${protocol}${hostname}/i2psnark/`;
|
|
}
|
|
|
|
/**
|
|
* Check if URL is extension URL
|
|
* @param {Object} details Request details
|
|
* @returns {boolean} Whether URL is extension
|
|
*/
|
|
static isExtensionURL(details) {
|
|
return details && details.url && details.url.startsWith("moz-extension://");
|
|
}
|
|
|
|
// In scrub.js, URLManager class
|
|
|
|
static getRouterHostType(url) {
|
|
try {
|
|
const urlObj = new URL(url);
|
|
const isRouterConsole =
|
|
urlObj.port === "7657" &&
|
|
(urlObj.hostname === "127.0.0.1" || urlObj.hostname === "localhost");
|
|
|
|
if (!isRouterConsole) {
|
|
return null;
|
|
}
|
|
|
|
// Map router console paths to container types
|
|
const routerPaths = {
|
|
"/i2ptunnel/": "i2ptunnelmgr",
|
|
"/i2ptunnelmgr": "i2ptunnelmgr",
|
|
"/i2psnark/": "i2psnark",
|
|
"/torrents": "i2psnark",
|
|
"/susimail/": "webmail",
|
|
"/webmail": "webmail",
|
|
"/i2pbote/": "i2pbote",
|
|
"/console": "routerconsole",
|
|
"/home": "routerconsole",
|
|
};
|
|
|
|
for (const [path, type] of Object.entries(routerPaths)) {
|
|
if (urlObj.pathname.startsWith(path)) {
|
|
return type;
|
|
}
|
|
}
|
|
|
|
// Default to router console for root path
|
|
if (urlObj.pathname === "/" || urlObj.pathname === "") {
|
|
return "routerconsole";
|
|
}
|
|
} catch (error) {
|
|
console.error("Router host type check failed:", error);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Page Action Manager for browser toolbar icons
|
|
*/
|
|
class PageActionManager {
|
|
/**
|
|
* Set up page action for tab
|
|
* @param {Object} tab Browser tab
|
|
*/
|
|
static async setupPageAction(tab) {
|
|
try {
|
|
if (!tab || !tab.id || !tab.url) {
|
|
return;
|
|
}
|
|
|
|
const isHttps = tab.url.startsWith("https://");
|
|
const isI2p = tab.url.includes(".i2p");
|
|
|
|
if (isHttps && isI2p) {
|
|
await this.setI2PSecurePageAction(tab);
|
|
} else if (isHttps) {
|
|
await this.checkI2PLocation(tab);
|
|
} else if (isI2p) {
|
|
await this.setI2PPageAction(tab);
|
|
}
|
|
|
|
await this.checkTorrentLocation(tab);
|
|
} catch (error) {
|
|
console.error("Page action setup failed:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up security popup
|
|
* @param {number} tabId Tab ID
|
|
*/
|
|
static async setupSecurityPopup(tabId) {
|
|
try {
|
|
await Promise.all([
|
|
browser.pageAction.setPopup({
|
|
tabId: tabId,
|
|
popup: CONTAINER_CONFIG.URLS.POPUPS.SECURITY,
|
|
}),
|
|
browser.pageAction.show(tabId),
|
|
]);
|
|
} catch (error) {
|
|
console.error("Security popup setup failed:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set I2P secure page action
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
*/
|
|
static async setI2PSecurePageAction(tab) {
|
|
await Promise.all([
|
|
browser.pageAction.setPopup({
|
|
tabId: tab.id,
|
|
popup: CONTAINER_CONFIG.URLS.POPUPS.SECURITY,
|
|
}),
|
|
browser.pageAction.setIcon({
|
|
path: CONTAINER_CONFIG.URLS.ICONS.INFOTOOPIES,
|
|
tabId: tab.id,
|
|
}),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Set I2P page action
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
*/
|
|
static async setI2PPageAction(tab) {
|
|
await Promise.all([
|
|
browser.pageAction.setPopup({
|
|
tabId: tab.id,
|
|
popup: CONTAINER_CONFIG.URLS.POPUPS.SECURITY,
|
|
}),
|
|
browser.pageAction.setIcon({
|
|
path: CONTAINER_CONFIG.URLS.ICONS.INFOTOOPIE,
|
|
tabId: tab.id,
|
|
}),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Check for I2P location
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
*/
|
|
static async checkI2PLocation(tab) {
|
|
try {
|
|
const response = await browser.tabs.sendMessage(tab.id, {
|
|
req: "i2p-location",
|
|
});
|
|
|
|
if (
|
|
response &&
|
|
response.content &&
|
|
response.content.toUpperCase() !== "NO-ALT-LOCATION"
|
|
) {
|
|
await this.setLocationPageAction(tab, response.content);
|
|
}
|
|
} catch (error) {
|
|
console.debug("No I2P location found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for torrent location
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
*/
|
|
static async checkTorrentLocation(tab) {
|
|
try {
|
|
const response = await browser.tabs.sendMessage(tab.id, {
|
|
req: "i2p-torrentlocation",
|
|
});
|
|
|
|
if (
|
|
response &&
|
|
response.content &&
|
|
response.content.toUpperCase() !== "NO-ALT-LOCATION"
|
|
) {
|
|
await this.setTorrentPageAction(tab, response.content);
|
|
}
|
|
} catch (error) {
|
|
console.debug("No torrent location found");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set location page action
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
* @param {string} location I2P location
|
|
*/
|
|
static async setLocationPageAction(tab, location) {
|
|
await Promise.all([
|
|
browser.pageAction.setPopup({
|
|
tabId: tab.id,
|
|
popup: CONTAINER_CONFIG.URLS.POPUPS.LOCATION,
|
|
}),
|
|
browser.pageAction.setIcon({
|
|
path: CONTAINER_CONFIG.URLS.ICONS.I2P,
|
|
tabId: tab.id,
|
|
}),
|
|
browser.pageAction.setTitle({
|
|
tabId: tab.id,
|
|
title: location,
|
|
}),
|
|
browser.pageAction.show(tab.id),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Set torrent page action
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
* @param {string} location Torrent location
|
|
*/
|
|
static async setTorrentPageAction(tab, location) {
|
|
await Promise.all([
|
|
browser.pageAction.setPopup({
|
|
tabId: tab.id,
|
|
popup: CONTAINER_CONFIG.URLS.POPUPS.TORRENT,
|
|
}),
|
|
browser.pageAction.setIcon({
|
|
path: CONTAINER_CONFIG.URLS.ICONS.INFOTOOPIEBT,
|
|
tabId: tab.id,
|
|
}),
|
|
browser.pageAction.setTitle({
|
|
tabId: tab.id,
|
|
title: location,
|
|
}),
|
|
browser.pageAction.show(tab.id),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event Manager for browser events
|
|
*/
|
|
class EventManager {
|
|
/**
|
|
* Initialize event listeners
|
|
*/
|
|
|
|
static initializeListeners() {
|
|
// Web request events with proper binding
|
|
browser.webRequest.onBeforeRequest.addListener(
|
|
(requestDetails) => RequestManager.handleRequest(requestDetails), // Use arrow function to preserve context
|
|
{
|
|
urls: [
|
|
"*://*.i2p/*",
|
|
"*://localhost/*",
|
|
"*://127.0.0.1/*",
|
|
"*://*/*i2p*",
|
|
],
|
|
}
|
|
);
|
|
|
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
|
(requestDetails) => HeaderManager.processHeaders(requestDetails), // Use arrow function to preserve context
|
|
{ urls: ["*://*.i2p/*"] },
|
|
["requestHeaders", "blocking"]
|
|
);
|
|
|
|
// Tab events with proper binding
|
|
const tabEvents = [
|
|
"onActivated",
|
|
"onAttached",
|
|
"onCreated",
|
|
"onDetached",
|
|
"onHighlighted",
|
|
"onMoved",
|
|
"onReplaced",
|
|
];
|
|
|
|
tabEvents.forEach((event) => {
|
|
browser.tabs[event].addListener(
|
|
async (info) => await EventManager.handleTabEvent(info) // Use arrow function to preserve context
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle tab event
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
*/
|
|
static async handleTabEvent(tab) {
|
|
try {
|
|
if (typeof tab === "number") {
|
|
const tabInfo = await browser.tabs.get(tab);
|
|
await PageActionManager.setupPageAction(tabInfo);
|
|
} else if (tab && typeof tab.tabId === "number") {
|
|
const tabInfo = await browser.tabs.get(tab.tabId);
|
|
await PageActionManager.setupPageAction(tabInfo);
|
|
} else if (tab && Array.isArray(tab.tabIds)) {
|
|
for (const tabId of tab.tabIds) {
|
|
const tabInfo = await browser.tabs.get(tabId);
|
|
await PageActionManager.setupPageAction(tabInfo);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Tab event handling failed:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle headers
|
|
* @private
|
|
* @param {Object} headers Header details
|
|
* @returns {Promise<Object>} Modified headers
|
|
*/
|
|
static handleHeaders(headers) {
|
|
return new Promise((resolve) => {
|
|
window.setTimeout(() => {
|
|
if (headers.tabId !== undefined) {
|
|
browser.pageAction
|
|
.getPopup({ tabId: headers.tabId })
|
|
.then(PageActionManager.setupPageAction);
|
|
}
|
|
|
|
resolve({ responseHeaders: headers.responseHeaders });
|
|
}, 2000);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Request Manager for handling browser requests
|
|
*/
|
|
class RequestManager {
|
|
/**
|
|
* Handle web request
|
|
* @param {Object} requestDetails Request details
|
|
* @returns {Promise<Object>} Modified request
|
|
*/
|
|
static async handleRequest(requestDetails) {
|
|
try {
|
|
if (!requestDetails) {
|
|
return requestDetails;
|
|
}
|
|
|
|
// Handle proxy requests
|
|
if (RequestManager.isProxyRequest(requestDetails)) {
|
|
await RequestManager.handleProxyRequest(requestDetails);
|
|
return requestDetails;
|
|
}
|
|
|
|
// Handle tab requests
|
|
if (requestDetails.tabId > 0) {
|
|
return await RequestManager.handleTabRequest(requestDetails);
|
|
}
|
|
|
|
return requestDetails;
|
|
} catch (error) {
|
|
console.error("Request handling failed:", error);
|
|
return requestDetails;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle proxy request
|
|
* @private
|
|
* @param {Object} requestDetails Request details
|
|
*/
|
|
static async handleProxyRequest(requestDetails) {
|
|
try {
|
|
await browser.cookies.set({
|
|
firstPartyDomain: this.getI2PHostname(requestDetails.url),
|
|
url: requestDetails.url,
|
|
secure: true,
|
|
});
|
|
} catch (error) {
|
|
console.error("Proxy request handling failed:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle tab request
|
|
* @private
|
|
* @param {Object} requestDetails Request details
|
|
* @returns {Promise<Object>} Modified request
|
|
*/
|
|
static async handleTabRequest(requestDetails) {
|
|
try {
|
|
const tab = await UITabManager.getTab(requestDetails.tabId);
|
|
if (!tab) {
|
|
return requestDetails;
|
|
}
|
|
|
|
const url = requestDetails.url;
|
|
const routerHost = URLManager.getRouterHostType(url);
|
|
const localHost = this.getLocalHostType(url);
|
|
|
|
// Handle router console requests (127.0.0.1:7657)
|
|
if (routerHost) {
|
|
return await this.handleRouterRequest(tab, routerHost, requestDetails);
|
|
}
|
|
|
|
// Handle other local services
|
|
if (localHost) {
|
|
return await this.handleLocalRequest(tab, localHost, requestDetails);
|
|
}
|
|
|
|
// Handle I2P host requests
|
|
if (this.isI2PHost(requestDetails)) {
|
|
return await this.handleI2PRequest(tab, requestDetails);
|
|
}
|
|
|
|
return requestDetails;
|
|
} catch (error) {
|
|
console.error("Tab request handling failed:", error);
|
|
return requestDetails;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle router request
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
* @param {string} routerHost Router host type
|
|
* @param {Object} requestDetails Request details
|
|
* @returns {Promise<Object>} Modified request
|
|
*/
|
|
static async handleRouterRequest(tab, routerHost, requestDetails) {
|
|
const routerMap = {
|
|
i2ptunnelmgr: {
|
|
handler: this.handleTunnelRequest,
|
|
container: CONTAINER_CONFIG.MESSAGES.TUNNEL,
|
|
},
|
|
i2psnark: {
|
|
handler: this.handleTorrentRequest,
|
|
container: CONTAINER_CONFIG.MESSAGES.TORRENT,
|
|
},
|
|
webmail: {
|
|
handler: this.handleMailRequest,
|
|
container: CONTAINER_CONFIG.MESSAGES.MAIL,
|
|
},
|
|
i2pbote: {
|
|
handler: this.handleBoteRequest,
|
|
container: CONTAINER_CONFIG.MESSAGES.BOTE,
|
|
},
|
|
routerconsole: {
|
|
handler: this.handleConsoleRequest,
|
|
container: CONTAINER_CONFIG.MESSAGES.ROUTER,
|
|
},
|
|
};
|
|
|
|
const service = routerMap[routerHost];
|
|
if (!service) {
|
|
return requestDetails;
|
|
}
|
|
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
service.container,
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Handle local request
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
* @param {string} localHost Local host type
|
|
* @param {Object} requestDetails Request details
|
|
* @returns {Promise<Object>} Modified request
|
|
*/
|
|
static async handleLocalRequest(tab, localHost, requestDetails) {
|
|
const localMap = {
|
|
blog: this.handleBlogRequest,
|
|
irc: this.handleIRCRequest,
|
|
tor: this.handleTorRequest,
|
|
};
|
|
|
|
const handler = localMap[localHost];
|
|
if (!handler) {
|
|
return requestDetails;
|
|
}
|
|
|
|
return await handler.call(this, tab, requestDetails);
|
|
}
|
|
|
|
/**
|
|
* Handle I2P request
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
* @param {Object} requestDetails Request details
|
|
* @returns {Promise<Object>} Modified request
|
|
*/
|
|
static async handleI2PRequest(tab, requestDetails) {
|
|
try {
|
|
await this.handleProxyRequest(requestDetails);
|
|
|
|
// Only containerize if not already in a container
|
|
if (!ContainerManager.isInContainer(tab)) {
|
|
const containerTab = await UITabManager.processContainerTab(
|
|
tab,
|
|
CONTAINER_CONFIG.MESSAGES.TITLE,
|
|
false
|
|
);
|
|
|
|
if (containerTab) {
|
|
await UITabManager.updateTab(containerTab, requestDetails.url);
|
|
}
|
|
}
|
|
|
|
return requestDetails;
|
|
} catch (error) {
|
|
console.error("I2P request handling failed:", error);
|
|
return requestDetails;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Service-specific request handlers
|
|
*/
|
|
static async handleTunnelRequest(tab, requestDetails) {
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
CONTAINER_CONFIG.MESSAGES.TUNNEL,
|
|
true
|
|
);
|
|
}
|
|
|
|
static async handleTorrentRequest(tab, requestDetails) {
|
|
const url = URLManager.fixTorrentURL(requestDetails.url);
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
{ ...requestDetails, url },
|
|
CONTAINER_CONFIG.MESSAGES.TORRENT,
|
|
true
|
|
);
|
|
}
|
|
|
|
static async handleMailRequest(tab, requestDetails) {
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
CONTAINER_CONFIG.MESSAGES.MAIL,
|
|
true
|
|
);
|
|
}
|
|
|
|
static async handleBoteRequest(tab, requestDetails) {
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
CONTAINER_CONFIG.MESSAGES.BOTE,
|
|
true
|
|
);
|
|
}
|
|
|
|
static async handleConsoleRequest(tab, requestDetails) {
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
CONTAINER_CONFIG.MESSAGES.ROUTER,
|
|
true
|
|
);
|
|
}
|
|
|
|
static async handleBlogRequest(tab, requestDetails) {
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
CONTAINER_CONFIG.MESSAGES.BLOG,
|
|
true
|
|
);
|
|
}
|
|
|
|
static async handleIRCRequest(tab, requestDetails) {
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
CONTAINER_CONFIG.MESSAGES.IRC,
|
|
true
|
|
);
|
|
}
|
|
|
|
static async handleTorRequest(tab, requestDetails) {
|
|
return await this.processServiceRequest(
|
|
tab,
|
|
requestDetails,
|
|
CONTAINER_CONFIG.MESSAGES.TOR,
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Process service request
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
* @param {Object} requestDetails Request details
|
|
* @param {string} containerName Container name
|
|
* @param {boolean} pin Whether to pin tab
|
|
* @returns {Promise<Object>} Modified request
|
|
*/
|
|
/**
|
|
* Process service request
|
|
* @private
|
|
* @param {Object} tab Browser tab
|
|
* @param {Object} requestDetails Request details
|
|
* @param {string} containerName Container name
|
|
* @param {boolean} pin Whether to pin tab
|
|
* @returns {Promise<Object>} Modified request
|
|
*/
|
|
static async processServiceRequest(tab, requestDetails, containerName, pin) {
|
|
try {
|
|
// Only containerize if not already in a container
|
|
if (!ContainerManager.isInContainer(tab)) {
|
|
const containerTab = await UITabManager.processContainerTab(
|
|
tab,
|
|
containerName,
|
|
pin
|
|
);
|
|
|
|
if (containerTab) {
|
|
await UITabManager.updateTab(containerTab, requestDetails.url);
|
|
}
|
|
}
|
|
|
|
return requestDetails;
|
|
} catch (error) {
|
|
console.error("Service request processing failed:", error);
|
|
return requestDetails;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility methods
|
|
*/
|
|
static isProxyRequest(details) {
|
|
if (details.url) {
|
|
let url = new URL(details.url);
|
|
return url.hostname === "proxy.i2p";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static isI2PHost(details) {
|
|
if (details.url) {
|
|
let url = new URL(details.url);
|
|
return url.hostname.endsWith(".i2p");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static getI2PHostname(url) {
|
|
try {
|
|
return new URL(url).hostname;
|
|
} catch (error) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get local host type
|
|
* @private
|
|
* @param {string} url URL to check
|
|
* @returns {string|null} Local host type
|
|
*/
|
|
static getLocalHostType(url) {
|
|
try {
|
|
const urlObj = new URL(url);
|
|
const isLocalhost =
|
|
urlObj.hostname === "127.0.0.1" || urlObj.hostname === "localhost";
|
|
|
|
if (!isLocalhost) {
|
|
return null;
|
|
}
|
|
|
|
// Map local service ports to container types
|
|
const localServices = {
|
|
7669: "irc", // I2P IRC
|
|
7695: "tor", // Tor proxy
|
|
8084: "blog", // Local blog
|
|
};
|
|
|
|
return localServices[urlObj.port] || null;
|
|
} catch (error) {
|
|
console.error("Local host type check failed:", error);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize event listeners
|
|
EventManager.initializeListeners();
|
|
|
|
// Export for testing
|
|
if (typeof module !== "undefined" && module.exports) {
|
|
module.exports = {
|
|
HeaderManager,
|
|
UITabManager,
|
|
ContainerManager,
|
|
URLManager,
|
|
PageActionManager,
|
|
EventManager,
|
|
RequestManager,
|
|
CONTAINER_CONFIG,
|
|
};
|
|
}
|