mirror of
https://github.com/ION606/browser-chromium.git
synced 2026-05-14 22:26:56 +00:00
initial commit/backup
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import NodeCache from "node-cache";
|
||||
import fs from 'fs';
|
||||
import fsp from 'fs/promises';
|
||||
import path from "path";
|
||||
import { addTab } from "../serverJS/tabs_server.js";
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
import spawnWorker from "../serverJS/spawnworker.js";
|
||||
const { logger } = loggermod;
|
||||
|
||||
// go through all saved web pages and save them as a JSON object with ID: URL to load on browser start
|
||||
// delete the tabCache directory
|
||||
|
||||
// Path to the directory where cached tab data will be saved
|
||||
export const CACHE_DIRECTORY = path.join(process.cwd(), 'cache', 'tabCache');
|
||||
if (!fs.existsSync(CACHE_DIRECTORY)) fs.mkdirSync(CACHE_DIRECTORY);
|
||||
|
||||
const cache = new NodeCache({
|
||||
checkperiod: 1,
|
||||
deleteOnExpire: true,
|
||||
errorOnMissing: false,
|
||||
useClones: false
|
||||
});
|
||||
|
||||
export async function updateTabUrl(tabId, view) {
|
||||
try {
|
||||
const url = await view.webContents.executeJavaScript('window.location.href');
|
||||
const fpath = path.join(CACHE_DIRECTORY, 'tabs.json');
|
||||
|
||||
let tabs = {};
|
||||
try {
|
||||
const fileData = await fsp.readFile(fpath, 'utf-8');
|
||||
tabs = JSON.parse(fileData);
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') return logger.info(err);
|
||||
}
|
||||
|
||||
tabs[tabId] = tabs[tabId] || url;
|
||||
await fsp.writeFile(fpath, JSON.stringify(tabs), 'utf-8');
|
||||
logger.info(`Updated URL for tab ${tabId}`);
|
||||
} catch (err) {
|
||||
logger.error(`Error updating tab URL: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function readPartOfFile(filePath, start = 0, length = 200) {
|
||||
// open the file in read-only mode
|
||||
const fileHandle = await fsp.open(filePath, 'r');
|
||||
|
||||
try {
|
||||
const buffer = Buffer.alloc(length);
|
||||
const { bytesRead } = await fileHandle.read(buffer, 0, length, start);
|
||||
const s = buffer.subarray(0, bytesRead).toString('utf8');
|
||||
|
||||
// regex to match the URL inside the HTML comment
|
||||
const commentRegex = /<!--\s*saved from url=\(\d+\)((https?|file):\/\/[^\s]+)\s*-->/i
|
||||
const match = s.match(commentRegex);
|
||||
|
||||
return (match && match[1]) ? match[1] : null;
|
||||
|
||||
} finally {
|
||||
await fileHandle.close();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function finalTabCleanup() {
|
||||
const jsonfile = `${CACHE_DIRECTORY}/tabs.json`,
|
||||
fnames = fs.readdirSync(`${CACHE_DIRECTORY}`, { withFileTypes: true })
|
||||
.filter(o => o.isFile() && !o.name.endsWith('.json'));
|
||||
|
||||
const jsonconf = (fs.existsSync(jsonfile)) ? JSON.parse(fs.readFileSync(jsonfile, 'utf-8')) : {};
|
||||
|
||||
const jsonconfnew = await Promise.all(fnames.map(async ({ name }) => {
|
||||
const fpath = `${CACHE_DIRECTORY}/${name}`,
|
||||
tabid = name.replace('.html', '');
|
||||
if (jsonconf[tabid]) return [tabid, jsonconf[tabid]];
|
||||
|
||||
const flink = await readPartOfFile(fpath);
|
||||
return [tabid, flink];
|
||||
}));
|
||||
|
||||
const o = Object.fromEntries(jsonconfnew);
|
||||
logger.info(o);
|
||||
|
||||
if (!fs.existsSync(`${process.cwd()}/cache/tabs.json`)) fs.writeFile(`${process.cwd()}/cache/tabs.json`, JSON.stringify(o), (err) => {
|
||||
if (err) logger.error(err);
|
||||
});
|
||||
fs.rmSync(CACHE_DIRECTORY, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
// Save tab state to disk when switching or closing a tab
|
||||
/**
|
||||
* @param {*} tabId
|
||||
* @param {Electron.WebContentsView} view
|
||||
*/
|
||||
export async function saveTabState(tabId, view) {
|
||||
return new Promise(async (resolve) => {
|
||||
const savePath = path.join(CACHE_DIRECTORY, `${tabId}.mhtml`);
|
||||
if (fs.existsSync(savePath)) {
|
||||
// fs.rmSync(`${CACHE_DIRECTORY}/${tabId}_files`, { recursive: true });
|
||||
fs.rmSync(savePath);
|
||||
}
|
||||
|
||||
try {
|
||||
view.webContents.savePage(savePath, 'MHTML').then(() => {
|
||||
spawnWorker('convertpage', [savePath, tabId]);
|
||||
updateTabUrl(tabId, view).then(() => resolve(true));
|
||||
});
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
resolve(false);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function getSavedTabs() {
|
||||
try {
|
||||
const tabpath = `${process.cwd()}/cache/tabs.json`;
|
||||
if (fs.existsSync(tabpath)) {
|
||||
const tabs = fs.readFileSync(tabpath, 'utf-8');
|
||||
// fs.rmSync(tabpath);
|
||||
return JSON.parse(tabs);
|
||||
}
|
||||
else return {};
|
||||
}
|
||||
catch (err) {
|
||||
logger.error(err);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function loadTabs(customSession, tabs) {
|
||||
try {
|
||||
for (const key in tabs) {
|
||||
await addTab(null, key, customSession, tabs[key]);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function removeTabData(tabId) {
|
||||
const savePath = path.join(CACHE_DIRECTORY, `${tabId}.mhtml`);
|
||||
if (fs.existsSync(savePath)) {
|
||||
fs.rmSync(`${CACHE_DIRECTORY}/${tabId}_files`, { recursive: true });
|
||||
fs.rmSync(savePath);
|
||||
}
|
||||
|
||||
const fpath = `${CACHE_DIRECTORY}/tabs.json`,
|
||||
tabs = (fs.existsSync(fpath)) ? JSON.parse(fs.readFileSync(fpath, 'utf-8')) : {};
|
||||
if (tabs[tabId]) delete tabs[tabId];
|
||||
fs.writeFileSync(fpath, JSON.stringify(tabs), 'utf-8');
|
||||
}
|
||||
|
||||
|
||||
export const getLoadPath = (tabId) => path.join(CACHE_DIRECTORY, `${tabId}.html`);
|
||||
|
||||
|
||||
if (process.argv?.at(2)?.trim() === '--ischildproc') finalTabCleanup();
|
||||
@@ -0,0 +1,33 @@
|
||||
import { session } from 'electron';
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
|
||||
const noflush = ['youtube.com', 'chatgpt.com']; // replace with your domains
|
||||
|
||||
async function flushCookies(customSession = session.defaultSession) {
|
||||
// session.defaultSession.cookies.flushStore();
|
||||
|
||||
// get all cookies from the default session
|
||||
const allCookies = await customSession.cookies.get({});
|
||||
|
||||
// filter out cookies from domains in the noflush array
|
||||
const cookiesToDelete = allCookies.filter(cookie => {
|
||||
return !noflush.some(domain => cookie.domain.includes(domain));
|
||||
});
|
||||
|
||||
// delete each cookie that is not in the noflush list
|
||||
for (const cookie of cookiesToDelete) {
|
||||
// create the URL that matches the cookie's domain
|
||||
const cookieUrl = `http${cookie.secure ? 's' : ''}://${cookie.domain.replace(/^\./, '')}${cookie.path}`;
|
||||
|
||||
try {
|
||||
await customSession.cookies.remove(cookieUrl, cookie.name);
|
||||
logger.info(`Deleted cookie: ${cookie.name} from ${cookie.domain}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to delete cookie: ${cookie.name} from ${cookie.domain}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default flushCookies;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { dialog } from 'electron';
|
||||
|
||||
export async function askUserQuestion(window, title, question) {
|
||||
const response = await dialog.showMessageBox(window, {
|
||||
buttons: ['Yes', 'No'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
title,
|
||||
message: question,
|
||||
});
|
||||
|
||||
return response.response === 0; // true if 'Yes' was clicked, false if 'No'
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import { addHistory, displayHistory, getHistory } from '../serverJS/history.cjs';
|
||||
import fs from 'fs';
|
||||
import { findPath } from './paths.js';
|
||||
import * as tabModule from '../serverJS/tabs_server.js';
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Electron.Session} customSession
|
||||
*/
|
||||
export default function init(customSession) {
|
||||
logger.info('ipc initiated');
|
||||
ipcMain.on('ping', (event) => {
|
||||
logger.info(`server recieved ping from ${event.sender.id}`);
|
||||
event.sender.send('pong');
|
||||
});
|
||||
|
||||
ipcMain.on('display-history', (event, uid) => displayHistory(uid, event.sender));
|
||||
ipcMain.handle('get-history', async (_, uid) => getHistory(uid));
|
||||
ipcMain.on('tab-open', (e, id) => tabModule.openTab(e, id, customSession));
|
||||
ipcMain.on('tab-close', (e, id) => tabModule.closeTab(e, id, customSession));
|
||||
ipcMain.on('tab-new', (e, id, url) => tabModule.addTab(e, id, customSession, url));
|
||||
|
||||
// TODO: add logic here to save/return site perms
|
||||
ipcMain.on('set-site-perms', (e, sitehostname) => console.log(sitehostname));
|
||||
ipcMain.on('get-site-perms', (e, sitehostname) => {
|
||||
console.log(sitehostname);
|
||||
e.sender.send('site-perms', { popups: false });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const renderer = (fs.readFileSync(await findPath('renderer.js'), 'utf-8')),
|
||||
optimize = (fs.readFileSync(await findPath('optimize.js'), 'utf-8'));
|
||||
|
||||
|
||||
/**
|
||||
* @param {BrowserWindow} mainWindow
|
||||
*/
|
||||
export async function startinject(mainWindow, uid) {
|
||||
// execute the script in the renderer process
|
||||
mainWindow.webContents.executeJavaScript(renderer);
|
||||
mainWindow.webContents.executeJavaScript(optimize);
|
||||
// mainWindow.webContents.executeJavaScript(tabs);
|
||||
|
||||
const title = await mainWindow.webContents.executeJavaScript('document.title');
|
||||
addHistory(uid, mainWindow.webContents.getURL(), 200, title);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
const { createLogger, transports, format } = require('winston');
|
||||
|
||||
const logger = createLogger({
|
||||
level: 'info', // default log level
|
||||
format: format.combine(
|
||||
format.timestamp(), // include a timestamp
|
||||
format.printf((info) => `${info.timestamp} [${info.level.toUpperCase()}]: ${info.message}`)
|
||||
),
|
||||
transports: [
|
||||
new transports.Console({
|
||||
format: format.combine(
|
||||
format.colorize(), // make the console output colorful
|
||||
format.simple() // simple log format for console
|
||||
),
|
||||
}),
|
||||
new transports.File({
|
||||
filename: 'logs/mainprocerror.log',
|
||||
level: 'error', // log only errors to this file
|
||||
}),
|
||||
new transports.File({
|
||||
filename: 'logs/combined.log', // log all levels to this file
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = { logger };
|
||||
@@ -0,0 +1,67 @@
|
||||
import { BrowserWindow } from 'electron';
|
||||
import fs from 'fs';
|
||||
import dns from 'dns';
|
||||
import path from 'path';
|
||||
|
||||
const history = (fs.readFileSync(path.resolve(import.meta.dirname, '../CSS', 'history.css')).toString()),
|
||||
tabs = (fs.readFileSync(path.resolve(import.meta.dirname, '../CSS', 'tabs.css')).toString());
|
||||
|
||||
export const isValidURL = (u) => {
|
||||
try { return new URL(u); }
|
||||
catch (err) { return false; }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {BrowserWindow} window
|
||||
* @param {String} hostname
|
||||
*/
|
||||
export async function addEl(window, hostname) {
|
||||
let src = '';
|
||||
switch (hostname) {
|
||||
case 'lite.duckduckgo.com': src = 'duckduckgo.css';
|
||||
break;
|
||||
|
||||
case 'www.youtube.com': src = 'youtube.css';
|
||||
break;
|
||||
|
||||
default: //logger.info(origin);
|
||||
}
|
||||
|
||||
const p = path.resolve(import.meta.dirname, '../CSS', src);
|
||||
console.log(p);
|
||||
if (src && fs.existsSync(p)) {
|
||||
const srccontent = fs.readFileSync(p).toString();
|
||||
window.webContents.insertCSS(srccontent);
|
||||
// window.webContents.executeJavaScript(`window.safeHTML.addStylesheet(undefined, \`${srccontent}\`)`);
|
||||
}
|
||||
|
||||
window.webContents.insertCSS(history);
|
||||
window.webContents.insertCSS(tabs);
|
||||
|
||||
// window.safdocument.addEventListener('')eHTML.addStylesheet(srccontent, `https://ion-local.${window.location.hostname}/${src}`);
|
||||
// window.safeHTML.addStylesheet(history, `https://ion-local.${window.location.hostname}/history.css`);
|
||||
|
||||
// window.webContents.executeJavaScript(`window.safeHTML.addStylesheet(undefined, \`${history}\`)`);
|
||||
// window.webContents.executeJavaScript(`window.safeHTML.addStylesheet(undefined, \`${tabs}\`)`);
|
||||
}
|
||||
|
||||
|
||||
export function checkInternetConnectivity() {
|
||||
return new Promise((resolve) => {
|
||||
// Check if a known domain can be resolved (e.g., Google DNS).
|
||||
dns.lookup('8.8.8.8', async (err) => {
|
||||
if (err && err.code === 'ENOTFOUND') {
|
||||
resolve(false); // Domain couldn't be resolved
|
||||
} else {
|
||||
try {
|
||||
resolve((await fetch('https://www.google.com')).ok);
|
||||
}
|
||||
catch (err) {
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { join } from 'path';
|
||||
import fs from 'fs';
|
||||
import { exec } from 'child_process';
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
|
||||
// was import.meta.dirname but was changed to better suit the app
|
||||
export const __dirname = process.cwd();
|
||||
|
||||
|
||||
/**
|
||||
* returns the full path and performs cursory validation
|
||||
* @returns {Promise<String>} the full pth
|
||||
* @param {String} fname
|
||||
*/
|
||||
export const findPath = (fname, absolute = false) => {
|
||||
try {
|
||||
return new Promise((resolve, reject) => {
|
||||
// use the find command to search for the file
|
||||
exec(`find -type f -name "${fname}"`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
return reject(`Error: ${stderr || error.message}`)
|
||||
}
|
||||
|
||||
// clean up the output and resolve with the first result, or null if not found
|
||||
const relativePath = stdout.trim().split('\n').filter(Boolean)[0] || null;
|
||||
|
||||
if (!relativePath) return;
|
||||
|
||||
const p = (absolute) ? join(__dirname, relativePath) : relativePath;
|
||||
resolve(p);
|
||||
});
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
return logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns the full path and performs cursory validation
|
||||
* @returns {String} the full pth
|
||||
* @param {String} fname
|
||||
*/
|
||||
const findPathOld = (fname, absolute = false) => {
|
||||
try {
|
||||
const ext = fname.match(/[^.]+$/)?.at(0);
|
||||
const base = (absolute) ? __dirname : '';
|
||||
|
||||
switch (ext) {
|
||||
case 'mhtml':
|
||||
case 'html': {
|
||||
let p = join(base, 'HTML', fname);
|
||||
if (!fs.existsSync(p)) p = join(base, 'cache', 'tabCache', fname);
|
||||
return p;
|
||||
}
|
||||
|
||||
case 'scss':
|
||||
case 'css': return join(base, 'CSS', fname);
|
||||
|
||||
case 'cjs':
|
||||
case 'js': {
|
||||
let p = join(base, 'JS', fname);
|
||||
if (!fs.existsSync(p)) p = join(base, 'serverJS', fname);
|
||||
if (!fs.existsSync(p)) p = join(base, 'utils', fname);
|
||||
if (!fs.existsSync(p)) p = join(base, 'organization', fname);
|
||||
if (fs.existsSync(p)) return p;
|
||||
}
|
||||
break;
|
||||
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
return logger.error(err);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { WebContentsView } from "electron";
|
||||
import { partitionName, agent, uid } from "../main.js";
|
||||
import { findPath } from "./paths.js";
|
||||
import { addEl, isValidURL } from "./misc.js";
|
||||
import { startinject } from "./ipc.js";
|
||||
import intercept, { noworker } from "../serverJS/intercept.js";
|
||||
import loggermod from '../utils/logger.cjs';
|
||||
const { logger } = loggermod;
|
||||
|
||||
|
||||
/**
|
||||
* @param {Electron.WebContents} contents
|
||||
*/
|
||||
export async function handleWebViewInit(contents) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} tabId
|
||||
* @param {Electron.BaseWindow} currentWindow
|
||||
*/
|
||||
export async function createWebview(tabId, currentWindow, customSession, preloadFname = 'preload.cjs') {
|
||||
const preloadPath = await findPath(preloadFname, true);
|
||||
logger.info(preloadFname, preloadPath);
|
||||
|
||||
const view = new WebContentsView({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: true, // allow access to Node.js in renderer
|
||||
javascript: true,
|
||||
plugins: true,
|
||||
// enableBlinkFeatures: "WebContentsForceDark",
|
||||
partition: partitionName,
|
||||
preload: preloadPath,
|
||||
nodeIntegrationInWorker: true
|
||||
}
|
||||
});
|
||||
view.id = tabId;
|
||||
|
||||
// set initial size
|
||||
const resizeWebView = () => {
|
||||
const { width: w, height: h } = currentWindow.getBounds();
|
||||
if (tabId === -1) view.setBounds({ x: 0, y: 0, width: w, height: 35 });
|
||||
else view.setBounds({ x: 0, y: 35, width: w, height: h - 35 });
|
||||
};
|
||||
|
||||
// add the web view as a child of the window's content view
|
||||
currentWindow.contentView.addChildView(view);
|
||||
|
||||
// update bounds on window resize
|
||||
currentWindow.on('resize', resizeWebView);
|
||||
|
||||
view.webContents.setUserAgent(agent);
|
||||
const { width: w, height: h } = currentWindow.getBounds();
|
||||
view.setBounds({ x: 0, y: 0, width: w, height: h });
|
||||
|
||||
view.webContents.on('did-start-navigation', (e, newU) => {
|
||||
const u = isValidURL(newU);
|
||||
if (noworker.find(o => u.hostname.match(o)) && customSession.protocol.isProtocolHandled('https')) customSession.protocol.unhandle('https');
|
||||
else if (!customSession.protocol.isProtocolHandled('https')) customSession.protocol.handle('https', (r) => intercept(r, uid));
|
||||
});
|
||||
|
||||
view.webContents.on('did-navigate', (e, newU) => {
|
||||
const u = isValidURL(newU);
|
||||
addEl(view, u?.hostname);
|
||||
startinject(view, uid);
|
||||
});
|
||||
|
||||
view.webContents.setBackgroundThrottling(true);
|
||||
resizeWebView();
|
||||
return view;
|
||||
}
|
||||
Reference in New Issue
Block a user