initial commit/backup

This commit is contained in:
2024-11-01 20:55:18 -04:00
commit bc53ce53b1
39 changed files with 10456 additions and 0 deletions
+165
View File
@@ -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();
+33
View File
@@ -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;
+13
View File
@@ -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'
}
+51
View File
@@ -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);
}
+26
View File
@@ -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 };
+67
View File
@@ -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);
}
}
});
});
}
+79
View File
@@ -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);
}
}
+72
View File
@@ -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;
}