mirror of
https://github.com/ION606/browser-chromium.git
synced 2026-06-06 00:07:06 +00:00
initial commit/backup
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { StaticNetFilteringEngine } from '@gorhill/ubo-core';
|
||||
import fs from 'fs/promises';
|
||||
import { checkInternetConnectivity } from '../utils/misc.js';
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
|
||||
const blocklists = [
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/badlists.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/filters.min.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/privacy.min.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/badware.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/quick-fixes.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/unbreak.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/annoyances.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/filters/lan-block.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easyprivacy.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-annoyances.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-cookies.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-newsletters.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-notifications.txt",
|
||||
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-social.txt",
|
||||
"https://raw.githubusercontent.com/laylavish/uBlockOrigin-HUGE-AI-Blocklist/main/list.txt"
|
||||
];
|
||||
|
||||
|
||||
async function fetchList(url) {
|
||||
return fetch(url).then(r => {
|
||||
return r.text();
|
||||
}).then(raw => {
|
||||
return { raw };
|
||||
}).catch(reason => {
|
||||
logger.error(reason);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const snfe = await StaticNetFilteringEngine.create();
|
||||
|
||||
// const rsf = await fetch('https://api.github.com/repos/uBlockOrigin/uAssets/contents/filters'),
|
||||
// safeLists = (await rsf.json()).map(o => o.download_url);
|
||||
|
||||
const pathToSelfie = 'cache/selfie.txt';
|
||||
|
||||
// Up to date serialization data (aka selfie) available?
|
||||
let selfie;
|
||||
const ageInDays = await fs.stat(pathToSelfie).then(stat => {
|
||||
const fileDate = new Date(stat.mtime);
|
||||
return (Date.now() - fileDate.getTime()) / (7 * 24 * 60 * 60);
|
||||
}).catch(() => Number.MAX_SAFE_INTEGER);
|
||||
|
||||
// Use a selfie if available and not older than 7 days
|
||||
if (ageInDays <= 7) {
|
||||
selfie = await fs.readFile(pathToSelfie, { encoding: 'utf8' })
|
||||
.then(data => typeof data === 'string' && data !== '' && data)
|
||||
.catch(() => { });
|
||||
if (typeof selfie === 'string') {
|
||||
await snfe.deserialize(selfie);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch filter lists if no up to date selfie available
|
||||
if (!selfie && (await checkInternetConnectivity())) {
|
||||
logger.info(`Fetching lists...`);
|
||||
await snfe.useLists(blocklists.map(fetchList).filter(o => o));
|
||||
|
||||
const selfie = await snfe.serialize();
|
||||
fs.mkdir('cache', { recursive: true });
|
||||
await fs.writeFile(pathToSelfie, selfie);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* runs ublock origin url safe-checking
|
||||
*/
|
||||
const blocked = (url, originURL = undefined, mimeType = undefined) => url ? snfe.matchRequest({ url, originURL, type: mimeType }) : false;
|
||||
export default blocked;
|
||||
@@ -0,0 +1,60 @@
|
||||
const { createClient } = require('redis');
|
||||
const { exec } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const loggermod = require('../utils/logger.cjs');
|
||||
const { logger } = loggermod;
|
||||
|
||||
|
||||
/** @type {import('redis').RedisClientType} */
|
||||
let client;
|
||||
|
||||
|
||||
async function addHistory(uid, query, code, title) {
|
||||
// logger.info(uid, query);
|
||||
return await client.lPush(`searchHistory:${uid}`, JSON.stringify({ title, query, timestamp: new Date(), code }));
|
||||
}
|
||||
|
||||
|
||||
async function getHistory(uid) {
|
||||
return await client.lRange(`searchHistory:${uid}`, 0, -1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {Electron.WebContents} webContents
|
||||
*/
|
||||
async function displayHistory(uid, webContents) {
|
||||
const history = JSON.stringify(await getHistory(uid));
|
||||
webContents.executeJavaScript(`showHistory(${history})`);
|
||||
}
|
||||
|
||||
|
||||
const quitRedis = () => client.quit().then(() => logger.info('redis quit')).catch(_ => null);
|
||||
|
||||
async function setupRedis() {
|
||||
await new Promise((resolve, reject) => {
|
||||
if (!fs.existsSync('../cache/redis.conf')) {
|
||||
fs.writeFileSync('../cache/redis.conf', `dir ./\ndbfilename dump.rdb`);
|
||||
}
|
||||
|
||||
const p = exec('redis-server ../cache/redis.conf', (err, stdout, stderr) => {
|
||||
if (err) return reject(err);
|
||||
});
|
||||
p.on('message', logger.info);
|
||||
p.on('error', logger.error);
|
||||
p.on('spawn', resolve);
|
||||
});
|
||||
|
||||
client = await createClient()
|
||||
.on('error', err => {
|
||||
logger.info('Redis Client Error', err);
|
||||
})
|
||||
.connect();
|
||||
|
||||
// clear history on browser boot
|
||||
client.flushDb();
|
||||
|
||||
logger.info('Redis Client Connected!');
|
||||
}
|
||||
|
||||
module.exports = { setupRedis, redisclient: client, getHistory, addHistory, displayHistory, quitRedis };
|
||||
@@ -0,0 +1,32 @@
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
import intercept from '../serverJS/intercept.js';
|
||||
import setUpShortcuts from '../serverJS/shortcuts.js';
|
||||
import { organizeTabIds } from '../serverJS/tabs_server.js';
|
||||
import { getSavedTabs, loadTabs } from '../utils/clearCache.js';
|
||||
import flushCookies from '../utils/cookies.js';
|
||||
import { askUserQuestion } from '../utils/dialogue.js';
|
||||
import ipcinit from '../utils/ipc.js';
|
||||
import { checkInternetConnectivity } from '../utils/misc.js';
|
||||
import { findPath } from '../utils/paths.js';
|
||||
import { createWebview, handleWebViewInit } from '../utils/webviewHelpers.js';
|
||||
|
||||
const { setupRedis, quitRedis } = await import('../serverJS/history.cjs');
|
||||
const { logger } = loggermod;
|
||||
|
||||
export {
|
||||
logger,
|
||||
intercept,
|
||||
setUpShortcuts,
|
||||
organizeTabIds,
|
||||
getSavedTabs,
|
||||
loadTabs,
|
||||
flushCookies,
|
||||
askUserQuestion,
|
||||
ipcinit,
|
||||
checkInternetConnectivity,
|
||||
findPath,
|
||||
createWebview,
|
||||
handleWebViewInit,
|
||||
setupRedis,
|
||||
quitRedis
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
import { findPath } from "../utils/paths.js";
|
||||
import blocked from "./adblock.js";
|
||||
import fs from "fs";
|
||||
import * as history from "./history.cjs";
|
||||
const { addHistory } = history;
|
||||
import { net, shell } from "electron";
|
||||
import spawnworker from "./spawnworker.js";
|
||||
import { checkInternetConnectivity } from "../utils/misc.js";
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
export const noworker = ['www.youtube.com', 'accounts.youtube.com', 'accounts.google.com', '.*\.googlevideo\.com'];
|
||||
|
||||
export const transformduckurl = (u) => {
|
||||
try {
|
||||
const urlParams = new URLSearchParams(new URL(u).search);
|
||||
const actualUrl = decodeURIComponent(urlParams.get('uddg'));
|
||||
return actualUrl;
|
||||
}
|
||||
catch (err) {
|
||||
console.warn(err);
|
||||
return u;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {GlobalRequest} req
|
||||
*/
|
||||
export default async function intercept(request, uid) {
|
||||
try {
|
||||
const u = new URL(request.url);
|
||||
|
||||
if (u.protocol === 'file:' || u.hostname.startsWith('ion-local')) {
|
||||
const filePath = await findPath(u.pathname.split('/')?.at(-1), true);
|
||||
|
||||
if (!filePath || !fs.existsSync(filePath)) return new Response(`file"${filePath}" not found!`, { status: 404 });
|
||||
|
||||
// read the file from the filesystem
|
||||
let fileData = fs.readFileSync(filePath).toString();
|
||||
|
||||
// guess the mime type (e.g., text/html, application/javascript)
|
||||
let mimeType = 'text/plain';
|
||||
if (filePath.endsWith('.html')) mimeType = 'text/html';
|
||||
else if (filePath.endsWith('.js')) mimeType = 'application/javascript';
|
||||
else if (filePath.endsWith('.css')) mimeType = 'text/css';
|
||||
|
||||
if (filePath.endsWith('HTML/nointernet.html')) {
|
||||
const hascon = await checkInternetConnectivity();
|
||||
const rNew = { 'Location': 'https://start.duckduckgo.com', 'Content-Type': 'text/html' };
|
||||
if (hascon) return new Response(Buffer.from(`<html><body>Loading...</body></html>`, 'utf-8'), { status: 301, headers: rNew });
|
||||
}
|
||||
|
||||
// send the file content along with a mime type
|
||||
return new Response(fileData, { headers: { 'Content-Type': mimeType } });
|
||||
}
|
||||
else {
|
||||
if (blocked(request.url)) return new Response('Request Blocked by UBlock Origin', { status: 503, statusText: 'Request Blocked by UBlock Origin' });
|
||||
|
||||
let newURL = request.url;
|
||||
|
||||
// force dark mode and turn off safe search
|
||||
if (u.hostname.includes('duckduckgo.com')) newURL += (u.search) ? '&kae=d&kp=-2' : '?kae=d';
|
||||
else if (u.hostname.includes('google.com')) newURL += (u.search) ? '&safe=off&&pccc=1' : '?pccc=1';
|
||||
|
||||
// here to avoid `TypeError: Cannot set property url of #<_Request> which has only a getter`
|
||||
try { request.url = newURL; }
|
||||
catch (_) { }
|
||||
|
||||
const iswebpagereq = request.method?.toUpperCase() === 'GET' && request.headers.get('Accept').includes('text/html');
|
||||
|
||||
// Odd duckduckgo redir thing (I hate it)
|
||||
if (request.url.startsWith('https://duckduckgo.com/l/?')) {
|
||||
const newURL = transformduckurl(request.url);
|
||||
const rNew = {
|
||||
'Location': newURL, // Set the redirect location header
|
||||
'Content-Type': 'text/html',
|
||||
}
|
||||
return new Response(Buffer.from(`<html><body>Redirecting to <a href="${newURL}">${newURL}</a>...</body></html>`, 'utf-8'), { status: 301, headers: rNew });
|
||||
}
|
||||
|
||||
let r;
|
||||
// special case
|
||||
if (u.href.match(/https:\/\/accounts\.(google|youtube)\.com\/(.*\/)?(signin\/challenge|ServiceLogin)\/?.*/gm)) {
|
||||
// const urlObj = new URL(u.href);
|
||||
|
||||
// // Decode the `continue` parameter and modify it
|
||||
// let continueUrl = decodeURIComponent(urlObj.searchParams.get('continue'));
|
||||
|
||||
// // You can modify the `continue` URL to use your custom protocol (myapp://callback)
|
||||
// continueUrl = 'iobrowser://callback';
|
||||
|
||||
// // Encode and set the updated `continue` parameter back in the original URL
|
||||
// urlObj.searchParams.set('continue', encodeURIComponent(continueUrl));
|
||||
|
||||
// // This is your modified URL that you will use to launch the browser
|
||||
// return shell.openExternal(urlObj.href, { logUsage: true, activate: true });
|
||||
|
||||
r = await net.fetch(request);
|
||||
}
|
||||
// sloppy fix
|
||||
else r = await net.fetch(request);
|
||||
// else if (iswebpagereq || noworker.find(o => u.hostname.match(o))) r = await net.fetch(request);
|
||||
// else r = await spawnworker(request, uid);
|
||||
|
||||
if (request.headers.get('Accept').includes('text/html')) {
|
||||
if (u.hostname === 'lite.duckduckgo.com') {
|
||||
const params = new URLSearchParams(u.search);
|
||||
addHistory(uid, `${u.href}?${body}`, r.status, `DuckDuckGo${params.has('q') ? (' - ' + params.get('q')) : ''}`);
|
||||
}
|
||||
else {
|
||||
// const res = await fetch(u.href, { method: 'HEAD' }).catch(_ => null);
|
||||
// logger.info(res);
|
||||
|
||||
// addHistory(uid, u.href, r.status, 'title!');
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
|
||||
/*
|
||||
REMOVED BECAUSE IT'S TOO EXPENSIVE (SIGKILL-ed)
|
||||
// https://accounts.google.com/v3/signin/_/AccountsSignInUi/browserinfo?f.sid=3210847140573431127&bl=boq_identityfrontendauthuiserver_20241015.01_p0&hl=en&_reqid=139420&rt=j
|
||||
if (request.url === 'https://www.youtube.com/' || skip.includes(u.hostname)) return net.fetch(request);
|
||||
|
||||
let newURL = request.url;
|
||||
if (u.hostname.includes('duckduckgo.com')) newURL += (u.search) ? '&kae=d&kp=-2' : '?kae=d';
|
||||
else if (u.hostname.includes('google.com')) newURL += (u.search) ? '&safe=off&&pccc=1' : '?pccc=1';
|
||||
|
||||
const r = await net.fetch(request);
|
||||
|
||||
return r;
|
||||
*/
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.error(request.url, err);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { session, globalShortcut } from "electron";
|
||||
import { changeZoom } from "../JS/display.js";
|
||||
import { getCurrentTab, getCurrentWindow } from "./tabs_server.js";
|
||||
import { logger } from "./imports.js";
|
||||
|
||||
|
||||
/**
|
||||
* @param {Electron.Event} e
|
||||
* @param {Electron.BrowserWindow} window
|
||||
*/
|
||||
export default async function setUpShortcuts(uid) {
|
||||
globalShortcut.register('Control+Shift+I', () => {
|
||||
console.log("A", getCurrentWindow().currentView);
|
||||
getCurrentWindow().currentView.webContents.toggleDevTools();
|
||||
// getCurrentWindow().isFocused() ? getCurrentTab()?.toggleDevTools() : null
|
||||
});
|
||||
globalShortcut.register('Control+H', () => getCurrentTab()?.webContents.executeJavaScript('window.electronAPI.displayHistory()'));
|
||||
|
||||
// zoom
|
||||
globalShortcut.register('Control+=', () => changeZoom(getCurrentTab(), true));
|
||||
globalShortcut.register('Control+-', () => changeZoom(getCurrentTab(), false));
|
||||
globalShortcut.register('Control+Plus', () => changeZoom(getCurrentTab(), false, true));
|
||||
|
||||
|
||||
globalShortcut.register('Control+T', () => window.webContents.executeJavaScript('window.tabAPI.addTab()'))
|
||||
|
||||
// window.webContents.on('did-navigate', async (_, url, code, stat) => {
|
||||
// if (isValidURL(url)?.hostname === 'lite.duckduckgo.com') return;
|
||||
|
||||
// const title = await window.webContents.executeJavaScript('document.title');
|
||||
// });
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Worker } from 'worker_threads';
|
||||
import { findPath } from '../utils/paths.js';
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
// need to find something for this to do, as it breaks a lot of stuff if used for requests
|
||||
|
||||
/**
|
||||
* runs the given function with the given args in a Nodejs Worker
|
||||
* @param {Function} fn
|
||||
* @param {any[]} args
|
||||
* @returns
|
||||
*/
|
||||
export default function spawnWorker(fn, args) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// find path to worker.js
|
||||
const workerScriptPath = await findPath('worker.js');
|
||||
|
||||
// create a new worker with request data
|
||||
const worker = new Worker(workerScriptPath, {
|
||||
workerData: { fn: fn.toString(), args },
|
||||
// workerData: request,
|
||||
});
|
||||
|
||||
logger.info(`spawning worker for ${fn}(${args})`);
|
||||
|
||||
worker.on('online', () => logger.info(`started worker for ${fn}(${args})`));
|
||||
|
||||
// handle messages from the worker
|
||||
worker.on('message', (msg) => {
|
||||
try {
|
||||
resolve(msg);
|
||||
} catch (err) {
|
||||
logger.error('Error creating response:', err);
|
||||
resolve(msg);
|
||||
}
|
||||
// terminate the worker when done (removed bc it results in interrupts)
|
||||
// worker.terminate().then(c => console.log(`${request.url} - ${c}`));
|
||||
});
|
||||
|
||||
// handle errors from the worker
|
||||
worker.on('error', (error) => {
|
||||
logger.error('Worker error:', error);
|
||||
resolve({ status: 500 });
|
||||
});
|
||||
|
||||
// handle worker exiting unexpectedly
|
||||
worker.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
logger.warn(`Worker exited with code ${code} for ${request.url}`);
|
||||
resolve({ status: 500 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import fs from 'fs';
|
||||
import mhtml2html from 'mhtml2html';
|
||||
import { BaseWindow, WebContentsView } from 'electron';
|
||||
import { createWebview } from '../utils/webviewHelpers.js';
|
||||
import { CACHE_DIRECTORY, getLoadPath, saveTabState } from '../utils/clearCache.js';
|
||||
import path from 'path';
|
||||
import { logger } from './imports.js';
|
||||
|
||||
const webViewContentsMap = {}; // Memory storage for active tabs
|
||||
|
||||
|
||||
/**
|
||||
* returns the focused window, if there is no focused window, returns the first one spawned
|
||||
*/
|
||||
const getCurrentWindow = () => {
|
||||
const allWins = BaseWindow.getAllWindows(),
|
||||
w = allWins.find((win) => win.isFocused());
|
||||
if (!w && allWins.length > 0) return allWins?.at(0);
|
||||
else return w;
|
||||
};
|
||||
/**
|
||||
* @returns {Electron.CrossProcessExports.WebContentsView | undefined}
|
||||
*/
|
||||
const getCurrentTab = () => {
|
||||
const cw = getCurrentWindow();
|
||||
return cw?.currentView;
|
||||
}
|
||||
|
||||
const settabqual = (tabId) => getCurrentTab().webContents.executeJavaScript('setQuality()').catch(err => logger.warn(`setting quality for window ${tabId} failed with reason:\`\`\`${err}\`\`\``));
|
||||
|
||||
|
||||
/**
|
||||
* Switch to the specified view by its ID.
|
||||
* @param {string | Electron.WebContentsView} tabId
|
||||
*/
|
||||
async function switchToView(tabId) {
|
||||
const currentWindow = getCurrentWindow();
|
||||
|
||||
/** @type {WebContentsView} */
|
||||
const viewData = (tabId instanceof WebContentsView) ? tabId : webViewContentsMap[tabId];
|
||||
|
||||
if (!viewData || !currentWindow) return;
|
||||
else if (viewData.id < 0) return; // Don't modify views with negative IDs
|
||||
|
||||
viewData.webContents.setBackgroundThrottling(false);
|
||||
|
||||
const id = tabId.id || tabId;
|
||||
|
||||
// Save the current active view state
|
||||
|
||||
// find the non-background-playing window
|
||||
/** @type {WebContentsView} */
|
||||
const oldView = currentWindow.contentView.children.find((view) => view instanceof WebContentsView && (view.id >= 0 && !view.webContents.isCurrentlyAudible()));
|
||||
|
||||
// undo the optimizations
|
||||
const undoOptimize = async () => {
|
||||
await viewData.webContents.executeJavaScript('revertQuality()');
|
||||
viewData.setVisible(true);
|
||||
}
|
||||
|
||||
if (oldView) {
|
||||
// REMOVEME
|
||||
// currentWindow.contentView.removeChildView(oldView);
|
||||
|
||||
console.log(`saving`, JSON.stringify(id));
|
||||
// DO NOT AWAIT THIS CALL FFS, INEFFICIENT!!!
|
||||
saveTabState(oldView.id, oldView).then(() => console.log(`saved ${id}`));
|
||||
currentWindow.contentView.removeChildView(oldView);
|
||||
if (viewData.webContents.isCurrentlyAudible()) return undoOptimize();
|
||||
}
|
||||
else if (viewData?.webContents?.isCurrentlyAudible()) return undoOptimize();
|
||||
|
||||
// Set the new view as active and add it to the window
|
||||
// viewData.webContents.setBackgroundThrottling(true);
|
||||
currentWindow.contentView.addChildView(viewData);
|
||||
await viewData.webContents.loadURL('https://start.duckduckgo.com');
|
||||
|
||||
currentWindow.contentView.children.map(c => c.setVisible((c.id === id) || c.id < 0));
|
||||
currentWindow.contentView.children.map(c => console.log(c.id, c.webContents.isCurrentlyAudible()));
|
||||
settabqual(tabId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* moves the tab to the "background" and tries to minimize the footprint
|
||||
* @param {Electron.BaseWindow} currentWindow
|
||||
* @param {String} tabId
|
||||
* @param {Electron.WebContentsView} oldView
|
||||
*/
|
||||
async function shiftTabToBK(currentWindow, tabId, oldView, customSession) {
|
||||
try {
|
||||
webViewContentsMap[oldView.id] = oldView;
|
||||
// this is currently playing stuff, keep it open
|
||||
oldView.webContents.setBackgroundThrottling(false);
|
||||
oldView.setVisible(false);
|
||||
|
||||
// TODO: optimize the page more
|
||||
settabqual(tabId);
|
||||
|
||||
const newView = await createWebview(tabId, currentWindow, customSession);
|
||||
webViewContentsMap[tabId] = newView;
|
||||
|
||||
switchToView(newView);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new tab with caching.
|
||||
* @param {string} tabId
|
||||
* @param {Electron.Session} customSession
|
||||
* @param {string} [url]
|
||||
*/
|
||||
async function addTab(event, tabId, customSession, url = 'https://duckduckgo.com', isOpen = false) {
|
||||
const currentWindow = getCurrentWindow();
|
||||
const tabPath = getLoadPath(tabId),
|
||||
currentTab = getCurrentTab();
|
||||
|
||||
if (currentTab?.webContents?.isCurrentlyAudible() && !isOpen) return shiftTabToBK(currentWindow, tabId, currentTab, customSession);
|
||||
else if (webViewContentsMap[tabId]?.webContents.isCurrentlyAudible()) return switchToView(tabId);
|
||||
const newView = await createWebview(tabId, currentWindow, customSession);
|
||||
webViewContentsMap[tabId] = newView;
|
||||
|
||||
if (tabPath && fs.existsSync(tabPath)) newView.webContents.loadFile(tabPath);
|
||||
else newView.webContents.loadURL(url);
|
||||
|
||||
switchToView(newView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a tab and save its state to disk.
|
||||
* @param {string} tabId
|
||||
*/
|
||||
async function closeTab(event, tabId) {
|
||||
const currentWindow = getCurrentWindow();
|
||||
const view = webViewContentsMap[tabId];
|
||||
|
||||
if (view && view.id >= 0) {
|
||||
await saveTabState(tabId, view.webContents);
|
||||
currentWindow.contentView.removeChildView(view);
|
||||
view.webContents.destroy();
|
||||
delete webViewContentsMap[tabId];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open an existing tab by restoring its cached state.
|
||||
* @param {string} tabId
|
||||
*/
|
||||
function openTab(event, tabId, customSession) {
|
||||
addTab(event, tabId, customSession, undefined, true); // Reopen the tab by calling addTab with its ID
|
||||
}
|
||||
|
||||
|
||||
function organizeTabIds() {
|
||||
const tabs = fs.readdirSync(CACHE_DIRECTORY, { withFileTypes: true })
|
||||
.filter(o => (o.isFile() && o.name.endsWith('.html')))
|
||||
.map(o => o.name);
|
||||
|
||||
const tmpcachepath = 'cache/tmp/tabs';
|
||||
fs.mkdirSync(tmpcachepath, { recursive: true });
|
||||
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
fs.cpSync(`${CACHE_DIRECTORY}/${tabs[i]}`, `${tmpcachepath}/${i}.html`);
|
||||
}
|
||||
|
||||
fs.rmSync(CACHE_DIRECTORY, { recursive: true });
|
||||
fs.cpSync(tmpcachepath, CACHE_DIRECTORY, { recursive: true });
|
||||
fs.rmSync(tmpcachepath, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
export { closeTab, addTab, openTab, getCurrentWindow, getCurrentTab, organizeTabIds };
|
||||
@@ -0,0 +1,56 @@
|
||||
import { parentPort, workerData } from 'worker_threads';
|
||||
|
||||
import fs from 'fs';
|
||||
import mhtml2html from "mhtml2html";
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
|
||||
function MHTMLtoHTML(savePath, tabId) {
|
||||
return new Promise((resolve) => {
|
||||
// read the MHTML file content
|
||||
const mhtmlContent = fs.readFileSync(savePath, 'utf8');
|
||||
|
||||
/** @type {JSDOM} */
|
||||
const parsedResult = mhtml2html.convert(mhtmlContent, { parseDOM: (html) => new JSDOM(html) });
|
||||
|
||||
// save the extracted HTML file
|
||||
const htmlPath = savePath.replace('.mhtml', '.html');
|
||||
fs.writeFile(htmlPath, parsedResult.serialize(), (err) => {
|
||||
if (err) {
|
||||
logger.error(`error saving ${tabId}`);
|
||||
return resolve(false);
|
||||
}
|
||||
fs.rmSync(savePath);
|
||||
resolve(true)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// a function to process the request data
|
||||
const processRequest = async (data) => {
|
||||
try {
|
||||
const { fn, args } = data;
|
||||
let result;
|
||||
|
||||
switch (fn) {
|
||||
case 'convertpage': result = await MHTMLtoHTML(...args);
|
||||
break;
|
||||
|
||||
default: console.log(`unknown function "${fn}(${args})`);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
// handle and report any errors
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
};
|
||||
|
||||
// read input from the workerData passed from the main thread and process it
|
||||
(async () => {
|
||||
const result = await processRequest(workerData);
|
||||
// send the response to the main process
|
||||
parentPort.postMessage(result);
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,37 @@
|
||||
// read input from stdin (from main process) and process it
|
||||
process.stdin.on('data', async (data) => {
|
||||
try {
|
||||
// parse the request data (assumed to be in JSON format)
|
||||
const requestData = JSON.parse(data.toString());
|
||||
|
||||
// create a new Request object using the parsed request data
|
||||
const request = new Request(requestData.url, {
|
||||
method: requestData.method || 'GET',
|
||||
headers: requestData.headers || {},
|
||||
body: requestData.body ? JSON.stringify(requestData.body) : undefined,
|
||||
params: requestData.params,
|
||||
query: requestData.query
|
||||
})
|
||||
|
||||
// perform the fetch using Node.js native Fetch API
|
||||
const response = await fetch(request);
|
||||
|
||||
// read the response as an array buffer
|
||||
const buffer = await response.arrayBuffer();
|
||||
|
||||
// create response object
|
||||
const responseObject = {
|
||||
success: true,
|
||||
headers: Object.fromEntries(Array.from(response.headers)),
|
||||
mimeType: response.headers.get('Content-Type') || 'application/octet-stream',
|
||||
data: Buffer.from(buffer).toString('base64'), // encode as base64 for transmission
|
||||
}
|
||||
|
||||
// write the response to stdout
|
||||
logger.info(JSON.stringify(responseObject));
|
||||
} catch (error) {
|
||||
// handle and report any errors
|
||||
const errorResponse = { success: false, error: error.message }
|
||||
process.stdout.write(JSON.stringify(errorResponse) + '\n');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { spawn } from 'child_process';
|
||||
import { findPath } from '../utils/paths.js';
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
export default function spawnworker(request, uid) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const child = spawn('node', [await findPath('worker.js')]);
|
||||
|
||||
const requestObject = {
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(Array.from(request.headers)),
|
||||
url: request.url,
|
||||
body: await request.text(),
|
||||
params: request.params,
|
||||
query: request.query,
|
||||
};
|
||||
|
||||
// send the request data to the child process
|
||||
child.stdin.write(JSON.stringify(requestObject) + '\n');
|
||||
|
||||
// Accumulate data from child process stdout
|
||||
let accumulatedData = '';
|
||||
|
||||
// handle response from the child process
|
||||
child.stdout.on('data', (data) => {
|
||||
accumulatedData += data.toString();
|
||||
|
||||
// Check if accumulatedData contains a complete JSON object
|
||||
if (accumulatedData.trim().endsWith('}')) {
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(accumulatedData);
|
||||
} catch (err) {
|
||||
logger.error('Failed to parse response from child process:', err);
|
||||
child.kill(1);
|
||||
resolve({ status: 500 });
|
||||
return;
|
||||
}
|
||||
|
||||
// If JSON is parsed successfully, resolve the promise
|
||||
try {
|
||||
// Decode the base64 data back to a buffer
|
||||
if (response.data) response.data = Buffer.from(response.data, 'base64');
|
||||
resolve(new Response(response.data || response, {
|
||||
headers: response.headers,
|
||||
status: response.status || 200,
|
||||
}));
|
||||
} catch (err) {
|
||||
logger.error('Error creating response:', err);
|
||||
resolve(response);
|
||||
}
|
||||
child.kill(0);
|
||||
|
||||
// Reset accumulatedData for the next potential message
|
||||
accumulatedData = '';
|
||||
}
|
||||
});
|
||||
|
||||
// handle errors from the child process
|
||||
child.stderr.on('data', (error) => {
|
||||
logger.error(`Child process stderr: ${error}`);
|
||||
resolve({ status: 500 });
|
||||
});
|
||||
|
||||
// handle if the child process exits unexpectedly
|
||||
child.on('close', (code) => {
|
||||
// not 0 or null
|
||||
if (!!code) {
|
||||
logger.error(`Child process exited with code ${code}`);
|
||||
resolve({ status: 500 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user