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,9 @@
|
||||
secrets/
|
||||
cache/
|
||||
node_modules/
|
||||
temp.*
|
||||
*.rdb
|
||||
*.log
|
||||
logs/
|
||||
temp/
|
||||
tmp/
|
||||
@@ -0,0 +1,163 @@
|
||||
/* general styles */
|
||||
body {
|
||||
background-color: #181818; /* darker background */
|
||||
color: #E0E0E0; /* light text for readability */
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #9BBFFF; /* more muted blue links */
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #74A0FF; /* slightly lighter blue on hover */
|
||||
}
|
||||
|
||||
/* header */
|
||||
#header {
|
||||
background-color: #212121; /* consistent dark background */
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #D9534F; /* softer red for DuckDuckGo title */
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* search bar */
|
||||
form {
|
||||
/* display: flex; */
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 50%;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: #2C2C2C;
|
||||
color: #E0E0E0;
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background-color: #74A0FF;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="submit"]:hover {
|
||||
background-color: #578AFF;
|
||||
}
|
||||
|
||||
/* search results */
|
||||
#results {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.result {
|
||||
padding: 15px;
|
||||
margin-bottom: 15px; /* more spacing between results */
|
||||
background-color: #222;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.result:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.result a {
|
||||
font-size: 18px;
|
||||
color: #9BBFFF;
|
||||
}
|
||||
|
||||
.result a:hover {
|
||||
color: #74A0FF;
|
||||
}
|
||||
|
||||
.result .snippet {
|
||||
color: #B0B0B0;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* buttons */
|
||||
button, input[type="submit"] {
|
||||
background-color: #505CFF;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #4266F5;
|
||||
}
|
||||
|
||||
#footer {
|
||||
background-color: #212121;
|
||||
color: #777;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#footer a {
|
||||
color: #9BBFFF;
|
||||
}
|
||||
|
||||
#footer a:hover {
|
||||
color: #74A0FF;
|
||||
}
|
||||
|
||||
/* fine-tune form elements */
|
||||
input[type="text"]::placeholder {
|
||||
color: #999; /* dimmed placeholder */
|
||||
}
|
||||
|
||||
input[type="text"]:focus {
|
||||
outline: none;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
input[type="submit"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.link-text {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
|
||||
/* scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
/* mobile responsiveness */
|
||||
@media (max-width: 768px) {
|
||||
input[type="text"] {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#historybar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 20%;
|
||||
min-width: 100px;
|
||||
margin-left: -100%; /* initially off-screen */
|
||||
transition: margin-left 0.5s ease; /* smooth slide-in/out */
|
||||
background-color: rgba(100, 100, 100, 0.6) !important; /* translucent black background */
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 2px;
|
||||
padding-left: 5px;
|
||||
z-index: 99999999999999999999999999999999999999999999999; /* fuck you in particular howtogeek and your goddamn sidebar */
|
||||
font-size: medium !important;
|
||||
color: lightblue !important;
|
||||
}
|
||||
|
||||
|
||||
#historybar table {
|
||||
border-collapse: separate; /* keep cells separate */
|
||||
border-spacing: 0 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
#historybar table td {
|
||||
cursor: pointer;
|
||||
/* padding: 5px; */
|
||||
border-bottom: 1px solid rgba(100, 100, 100, 0.8);
|
||||
}
|
||||
|
||||
|
||||
#historybar table td:hover {
|
||||
background-color: rgba(100, 100, 100, 0.8);;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* style.css */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
webview {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.open-webview {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#tabwebview {
|
||||
display: block !important;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
html {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ccc;
|
||||
overflow-y: hidden; /* no y overflow */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.browser-tabs {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto; /* allow horizontal scrolling if needed */
|
||||
scrollbar-width: thin; /* Firefox-specific scrollbar width */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
background-color: #f0f0f0;
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
transition: background-color 0.3s;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
white-space: nowrap; /* keep text on a single line */
|
||||
}
|
||||
|
||||
.tab:hover,
|
||||
.tab.active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.add-tab {
|
||||
padding: 7px 12px;
|
||||
border: none;
|
||||
background-color: #4b4b6b; /* dark purple-gray background */
|
||||
color: #ddd; /* light gray text color */
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.add-tab:hover {
|
||||
background-color: #3b3b5a; /* slightly darker shade for hover */
|
||||
color: #fff; /* lighter color on hover */
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/* general background and text */
|
||||
body,
|
||||
#page-manager,
|
||||
ytd-app {
|
||||
background-color: #11111b !important;
|
||||
/* dark purpleish black */
|
||||
color: #d1d1e9 !important;
|
||||
/* soft light purple text */
|
||||
}
|
||||
|
||||
/* video background */
|
||||
#player-container,
|
||||
#movie_player,
|
||||
.html5-video-player {
|
||||
background-color: #0f0f17 !important;
|
||||
/* darker background for video */
|
||||
}
|
||||
|
||||
/* sidebar and primary navigation */
|
||||
#container,
|
||||
ytd-guide-entry-renderer,
|
||||
ytd-mini-guide-renderer {
|
||||
background-color: #181828 !important;
|
||||
/* deep dark purple */
|
||||
}
|
||||
|
||||
#sections,
|
||||
ytd-guide-renderer {
|
||||
background-color: #1b1b2f !important;
|
||||
/* matches body */
|
||||
}
|
||||
|
||||
/* header and search bar */
|
||||
#masthead-container {
|
||||
background-color: #2a2a4d !important;
|
||||
/* dark purplish shade */
|
||||
}
|
||||
|
||||
#search-icon-legacy {
|
||||
color: #d1d1e9 !important;
|
||||
}
|
||||
|
||||
/* video title and descriptions */
|
||||
#video-title,
|
||||
.ytp-chrome-top,
|
||||
.ytp-title-text {
|
||||
color: #d1d1e9 !important;
|
||||
}
|
||||
|
||||
|
||||
/* video progress bar */
|
||||
.ytp-play-progress {
|
||||
background-color: #6b5b95 !important;
|
||||
/* purplish progress bar */
|
||||
}
|
||||
|
||||
.ytp-scrubber-button {
|
||||
background-color: #b794f4 !important;
|
||||
/* purplish scrubber */
|
||||
}
|
||||
|
||||
/* links and video titles */
|
||||
a,
|
||||
.yt-simple-endpoint {
|
||||
color: #b794f4 !important;
|
||||
/* purplish links */
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #d1d1e9 !important;
|
||||
/* lighter on hover */
|
||||
}
|
||||
|
||||
/* comments section */
|
||||
ytd-comment-thread-renderer {
|
||||
background-color: #181828 !important;
|
||||
/* dark purpleish black */
|
||||
border: 1px solid #262646 !important;
|
||||
}
|
||||
|
||||
ytd-comment-renderer #content-text {
|
||||
color: #d1d1e9 !important;
|
||||
}
|
||||
|
||||
/* misc elements */
|
||||
#related,
|
||||
#items,
|
||||
#panels {
|
||||
background-color: #1b1b2f !important;
|
||||
}
|
||||
|
||||
/* subscribe button */
|
||||
ytd-subscribe-button-renderer {
|
||||
background-color: #6b5b95 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
ytd-subscribe-button-renderer:hover {
|
||||
background-color: #9d7bcf !important;
|
||||
}
|
||||
|
||||
#search-form {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#voice-search-button .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--text {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
#voice-search-button .yt-spec-button-shape-next--mono.yt-spec-button-shape-next--text:hover {
|
||||
background-color: coral;
|
||||
}
|
||||
|
||||
#search-clear-button {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
ytd-button-renderer {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-spec-touch-feedback-shape yt-spec-touch-feedback-shape--touch-response:hover {
|
||||
background-color: rgba(255, 255, 255, 0.596);
|
||||
}
|
||||
|
||||
yt-icon.ytd-logo {
|
||||
color: inherit;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.guide-icon.ytd-guide-entry-renderer {
|
||||
color: white;
|
||||
color: white;
|
||||
}
|
||||
|
||||
html[darker-dark-theme], [darker-dark-theme] [light] {
|
||||
--yt-spec-text-primary: white;
|
||||
--ytd-searchbox-legacy-button-color: grey;
|
||||
--yt-spec-raised-background: rgb(20, 70, 37);
|
||||
--yt-spec-text-secondary: offwhite;
|
||||
--ytd-searchbox-text-color: white;
|
||||
|
||||
--yt-spec-icon-active-other: white;;
|
||||
--yt-spec-icon-inactive: darkgrey;
|
||||
--yt-spec-icon-disabled: darkgrey;
|
||||
--yt-spec-brand-icon-active: white;
|
||||
--yt-spec-brand-icon-inactive: grey;
|
||||
--yt-button-icon-button-text-color: black;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN echo "TODO: IMPLEMENT CONTAINERIZATION!!!"
|
||||
# RUN apt-get update && apt-get install neovim git nodejs curl lsb-core -y
|
||||
# RUN curl https://raw.githubusercontent.com/bluesky-social/pds/main/installer.sh > installer.sh
|
||||
|
||||
CMD ["bin/bash"]
|
||||
@@ -0,0 +1,22 @@
|
||||
<!-- index.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="height: 100%; width: 100%;">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>My Electron App</title>
|
||||
<link rel="stylesheet" href="../CSS/style.css">
|
||||
</head>
|
||||
|
||||
<body style="width: 100%; height: 100%; margin: 0; padding: 0;">
|
||||
<webview id="tabwebview" src="../HTML/tabs.html" style="width: auto; height: 35px; margin: 0; padding: 0; border: none;" nodeintegration preload="../organization/tabs.cjs"></webview>
|
||||
|
||||
<webview id="webview-0" src="https://duckduckgo.com" style="width: 100%; height: calc(100% - 40px);;" preload="JS/preload.cjs"
|
||||
partition="persist:myPartition"
|
||||
webpreferences="nodeIntegration=1, contextIsolation=1, javascript=1, plugins=1, enableBlinkFeatures=WebContentsForceDark"
|
||||
class="open-webview">
|
||||
</webview>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ION Browser Metrics</title>
|
||||
<script src="metrics.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>No Internet Connection</title>
|
||||
<style>
|
||||
/* dark mode background and text styling */
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #121212;
|
||||
color: #eeeeee;
|
||||
}
|
||||
|
||||
/* container styling and subtle shadow effect */
|
||||
.container {
|
||||
text-align: center;
|
||||
background: #1e1e1e;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
animation: fadeIn 1.5s ease-out;
|
||||
}
|
||||
|
||||
/* header styling with animation */
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
margin-bottom: 0.3em;
|
||||
animation: bounceIn 1.5s ease-out;
|
||||
}
|
||||
|
||||
/* paragraph styling and margin adjustments */
|
||||
p {
|
||||
font-size: 1.2em;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
/* keyframes for animations */
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
|
||||
0%,
|
||||
20%,
|
||||
40%,
|
||||
60%,
|
||||
80%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* button styling for retry */
|
||||
.retry-button {
|
||||
margin-top: 1.5em;
|
||||
padding: 0.7em 1.5em;
|
||||
font-size: 1em;
|
||||
color: #121212;
|
||||
background-color: #eeeeee;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* hover effect for retry button */
|
||||
.retry-button:hover {
|
||||
background-color: #f1c40f;
|
||||
color: #1e1e1e;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Connection Offline</h1>
|
||||
<p>It looks like you are not connected to the internet. Please check your connection and try again.</p>
|
||||
<button class="retry-button" onclick="window.location.reload()">Retry</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="margin: 0; padding: 0; height: min-content;">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
|
||||
<link rel="stylesheet" href="../CSS/tabs.css">
|
||||
</head>
|
||||
|
||||
<body style="margin: 0; padding: 0; height: fit-content">
|
||||
<div class="tabs-container">
|
||||
<div class="browser-tabs">
|
||||
<button class="tab" data-tab="0">Tab 0</button>
|
||||
</div>
|
||||
<button class="add-tab">+</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
// save the original window.open function
|
||||
const originalWindowOpen = window.open;
|
||||
|
||||
// override the window.open function
|
||||
window.open = function (url, target, features) {
|
||||
console.log('A new window is attempting to open:');
|
||||
console.log('URL:', url);
|
||||
console.log('Target:', target);
|
||||
console.log('Features:', features);
|
||||
|
||||
window.electronAPI.checkperms(window.location.hostname);
|
||||
|
||||
// call the original window.open function if you want the popup to proceed
|
||||
return originalWindowOpen.call(window, url, target, features);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* @param { @param {Electron.BrowserWindow} window} window
|
||||
*/
|
||||
export async function changeZoom(window, zoomIn = true, reset = false) {
|
||||
let zl = window.webContents.getZoomLevel();
|
||||
|
||||
if (reset) zl = 0
|
||||
else if (zoomIn) zl++;
|
||||
else zl--;
|
||||
|
||||
window.webContents.setZoomLevel(zl);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
async function waitForVideo() {
|
||||
return new Promise(resolve => {
|
||||
const i = setInterval(() => {
|
||||
if (document.querySelector('video')) {
|
||||
clearInterval(i);
|
||||
resolve(true);
|
||||
}
|
||||
}, 500)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function revertQuality() {
|
||||
setQuality(document?.body?.dataset?.oldel || 'auto');
|
||||
delete document?.body?.dataset?.oldel;
|
||||
}
|
||||
|
||||
|
||||
async function setQuality(quality = 'lowest') {
|
||||
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
await waitForVideo();
|
||||
console.info(`changing quality to ${quality}`);
|
||||
|
||||
const qualityMenuMain = [document.querySelector('.ytp-settings-menu'), document.querySelector('.ytp-panel-menu')],
|
||||
settingsButton = document.querySelector('.ytp-settings-button');
|
||||
|
||||
qualityMenuMain.map(el => el.style.opacity = '0');
|
||||
settingsButton.click();
|
||||
await sleep(500);
|
||||
|
||||
const qualityMenu = document.querySelector('.ytp-panel-menu').lastElementChild;
|
||||
qualityMenu.click();
|
||||
await sleep(500);
|
||||
|
||||
const qualityOptions = [...document.querySelector('.ytp-panel.ytp-quality-menu').querySelectorAll('.ytp-menuitem')];
|
||||
let selection;
|
||||
|
||||
if (quality === 'lowest') selection = qualityOptions.findLast(el => (!el.textContent.toLowerCase().includes('auto')));
|
||||
else selection = qualityOptions.find((el) => el.textContent.toLowerCase().includes(quality));
|
||||
|
||||
const currentQuality = qualityOptions.find(el => el.ariaChecked);
|
||||
if (currentQuality) document.body.dataset.oldel = currentQuality.textContent.toLowerCase();
|
||||
|
||||
if (!selection) {
|
||||
let qualityTexts = qualityOptions.map((el) => el.textContent).join('\n');
|
||||
console.info('"' + quality + '" not found. Options are: \n\nHighest\n' + qualityTexts + '\n' + 'setting to auto');
|
||||
settingsButton.click(); // click the menu button to close it
|
||||
selection = qualityOptions.findLast(el => (el.textContent.toLowerCase().includes('auto')));
|
||||
}
|
||||
|
||||
selection.click();
|
||||
qualityMenuMain.map(el => el.style.opacity = '1');
|
||||
}
|
||||
|
||||
|
||||
function optimize() {
|
||||
sessionStorage.setItem('ran-optimize', 1);
|
||||
console.info('quality tuning script loaded!');
|
||||
// setQuality('Highest');
|
||||
}
|
||||
|
||||
if (document.readyState === 'complete' && !sessionStorage.getItem('ran-optimize')) optimize();
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
|
||||
// renderer.js or script loaded in the renderer process
|
||||
const policy = window.trustedTypes.createPolicy('default', {
|
||||
createHTML: (input) => input, // policy allows only sanitized HTML strings
|
||||
});
|
||||
|
||||
process.once("loaded", () => {
|
||||
console.info('injecting...');
|
||||
contextBridge.exposeInMainWorld('safeHTML', {
|
||||
ping: () => console.info('pong'),
|
||||
write: (selector, htmlString) => {
|
||||
const element = document.querySelector(selector);
|
||||
if (element) element.innerHTML += policy.createHTML(htmlString);
|
||||
},
|
||||
insertBefore: (selector, htmlString) => {
|
||||
const element = document.querySelector(selector);
|
||||
if (element) element.innerHTML = policy.createHTML(htmlString) + element.innerHTML;
|
||||
},
|
||||
addScript: (content, src) => {
|
||||
// validate and securely add the script to the document's head
|
||||
const head = document.head;
|
||||
if (!head || (!src && !content)) return;
|
||||
|
||||
// create a script element
|
||||
const script = document.createElement('script');
|
||||
|
||||
if (content) script.innerText = content;
|
||||
else script.src = src;
|
||||
|
||||
// set script properties securely
|
||||
script.async = true;
|
||||
|
||||
// append to head
|
||||
head.appendChild(script);
|
||||
},
|
||||
addStylesheet: (href, inlineContent) => {
|
||||
const head = document.head;
|
||||
if (!head) return;
|
||||
|
||||
if (href) {
|
||||
// if href is provided, use it for external stylesheet
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.href = href;
|
||||
head.appendChild(link);
|
||||
} else if (inlineContent) {
|
||||
// create a style element for inline CSS
|
||||
const style = document.createElement('style');
|
||||
style.textContent = inlineContent;
|
||||
head.appendChild(style);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// TODO: replaceme
|
||||
const uid = 1;
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
displayHistory: () => ipcRenderer.send('display-history', uid), // send message to main process
|
||||
getHistory: () => ipcRenderer.invoke('get-history', uid), // request history data and wait for response
|
||||
showHistory: (history) => ipcRenderer.send('show-history', history), // sends data to the main process
|
||||
initTabs: () => ipcRenderer.send('init-tabs'),
|
||||
addTab: (url) => window.dispatchEvent(new CustomEvent('add-tab', { detail: url })),
|
||||
sendToMain: (channel, data) => {
|
||||
ipcRenderer.send(channel, data);
|
||||
},
|
||||
onReceive: (channel, func) => {
|
||||
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
||||
},
|
||||
checkperms: (sitehostname) => ipcRenderer.send('get-site-perms', sitehostname)
|
||||
});
|
||||
|
||||
// ipcRenderer.on('tab-opened', (ev, id) => {
|
||||
// document.querySelector('.open-webview')?.classList.remove('open-webview');
|
||||
// document.querySelector(`#webview-${id}`)?.classList.add('open-webview');
|
||||
// });
|
||||
|
||||
ipcRenderer.on('tab-created', (ev, id, url = 'https://duckduckgo.com') => createWebview(url, id));
|
||||
|
||||
|
||||
contextBridge.exposeInMainWorld('tabAPI', {
|
||||
ping: () => console.info('pong'),
|
||||
addTab: (url) => ipcRenderer.send('add-tab', url || 'about:blank')
|
||||
});
|
||||
|
||||
const load = () => {
|
||||
console.info("PRELOAD LOADED!");
|
||||
|
||||
if (window.location.origin === 'lite.duckduckgo.com') {
|
||||
document.body.querySelector('img[src="//duckduckgo.com/t/sl_l"]').remove();
|
||||
}
|
||||
|
||||
if (document.body) {
|
||||
// document.body.innerHTML = `<webview src="../HTML/tabs.html" style="flex:0 0.5 auto;" nodeintegration preload="../organization/tabs.cjs"></webview>` + document.body.innerHTML;
|
||||
ipcRenderer.send('init-tabs');
|
||||
}
|
||||
}
|
||||
|
||||
// document.onload = () => load;
|
||||
document.addEventListener('DOMContentLoaded', load);
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
function runinit() {
|
||||
console.info("added renderer script!");
|
||||
sessionStorage.setItem('ran-renderer', 1);
|
||||
forceDarkMode();
|
||||
}
|
||||
|
||||
|
||||
var removehbar = removehbar || function (e) {
|
||||
const hbar = document.querySelector('#historybar');
|
||||
if (!hbar || hbar.contains(e?.target)) return;
|
||||
|
||||
// slide out by setting margin-left to -100%
|
||||
hbar.style.marginLeft = '-100%';
|
||||
setTimeout(() => hbar.remove(), 500);
|
||||
|
||||
document.removeEventListener('click', removehbar);
|
||||
}
|
||||
|
||||
|
||||
function showHistory(...h) {
|
||||
if (Array.isArray(h[0])) h = h.flat(1);
|
||||
const hbar = document.querySelector('#historybar');
|
||||
if (hbar) return removehbar();
|
||||
|
||||
setTimeout(() => document.addEventListener('click', removehbar), 1000);
|
||||
|
||||
const sidebar = document.createElement('div');
|
||||
sidebar.id = 'historybar';
|
||||
sidebar.style.marginLeft = '-100%'; // start off-screen
|
||||
|
||||
const t = document.createElement('table'),
|
||||
tbody = document.createElement('tbody');
|
||||
|
||||
t.appendChild(tbody);
|
||||
|
||||
h.forEach(iraw => {
|
||||
const i = (typeof iraw === 'string') ? JSON.parse(iraw) : iraw,
|
||||
el = document.createElement('tr'),
|
||||
el2 = document.createElement('td'),
|
||||
a = document.createElement('a');
|
||||
|
||||
a.textContent = i.title;
|
||||
a.href = i.query;
|
||||
a.style.width = '100%';
|
||||
a.style.height = '100%';
|
||||
|
||||
el2.appendChild(a);
|
||||
el.appendChild(el2);
|
||||
tbody.appendChild(el);
|
||||
});
|
||||
|
||||
sidebar.appendChild(t);
|
||||
document.body.appendChild(sidebar);
|
||||
|
||||
// force reflow to apply the transition correctly
|
||||
window.getComputedStyle(sidebar).marginLeft;
|
||||
|
||||
// slide in by setting margin-left to 0
|
||||
sidebar.style.marginLeft = '0';
|
||||
}
|
||||
|
||||
|
||||
function forceDarkMode() {
|
||||
document.querySelector('[value="night"]')?.click(); // wikipedia
|
||||
|
||||
// manual
|
||||
// function to convert rgb values to a hex string
|
||||
const rgbToHex = (r, g, b) => {
|
||||
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
|
||||
}
|
||||
|
||||
// function to get the computed background and text color of the body
|
||||
const checkColors = () => {
|
||||
const body = document.querySelector('body') || document.querySelector('main') // select the body element
|
||||
const bgColor = window.getComputedStyle(body).backgroundColor // get background color
|
||||
const textColor = window.getComputedStyle(body).color // get text color
|
||||
|
||||
// parse the rgb values from the background and text color
|
||||
const bgMatch = bgColor.match(/\d+/g)
|
||||
const textMatch = textColor.match(/\d+/g)
|
||||
|
||||
// if both colors are in rgb format
|
||||
if (bgMatch && textMatch) {
|
||||
const bgHex = rgbToHex(parseInt(bgMatch[0]), parseInt(bgMatch[1]), parseInt(bgMatch[2]))
|
||||
const textHex = rgbToHex(parseInt(textMatch[0]), parseInt(textMatch[1]), parseInt(textMatch[2]))
|
||||
|
||||
// check if background is white (#FFFFFF) and text is dark (let's assume below #777777 as dark)
|
||||
if (bgHex === "#FFFFFF" && textHex <= "#777777") {
|
||||
// swap the background to dark and text to light
|
||||
body.style.backgroundColor = "#000000" // set background to black
|
||||
body.style.color = "#FFFFFF" // set text color to white
|
||||
}
|
||||
}
|
||||
}
|
||||
checkColors();
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', runinit);
|
||||
if (document.readyState === 'complete' && !sessionStorage.getItem('ran-renderer')) runinit();
|
||||
@@ -0,0 +1,144 @@
|
||||
// main.js
|
||||
import { app, BaseWindow, BrowserWindow, session, pushNotifications } from 'electron';
|
||||
import { exec } from 'child_process';
|
||||
import path from 'path';
|
||||
import {
|
||||
logger, intercept, setUpShortcuts, organizeTabIds,
|
||||
getSavedTabs, loadTabs, flushCookies, askUserQuestion,
|
||||
ipcinit, checkInternetConnectivity, findPath, createWebview,
|
||||
handleWebViewInit, setupRedis, quitRedis
|
||||
} from './serverJS/imports.js';
|
||||
|
||||
|
||||
await setupRedis();
|
||||
export const uid = 1,
|
||||
partitionName = 'persist:default',
|
||||
agent = 'Chrome';
|
||||
|
||||
// ensuring the userData directory is set
|
||||
app.setPath('userData', path.join(app.getPath('home'), '.ion-browser-data'));
|
||||
app.commandLine.appendSwitch('load-extension');
|
||||
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
app.setAsDefaultProtocolClient('ionbrowser', process.execPath, [path.resolve(process.argv[1])]);
|
||||
}
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient('ionbrowser');
|
||||
}
|
||||
|
||||
|
||||
async function createWindow(customSession) {
|
||||
organizeTabIds();
|
||||
|
||||
const mainWindow = new BaseWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
// backgroundColor: 'black',
|
||||
darkTheme: true,
|
||||
autoHideMenuBar: true,
|
||||
nodeIntegrationInWorker: true,
|
||||
});
|
||||
|
||||
const tabWebView = await createWebview(-1, mainWindow, customSession, 'tabs.cjs'),
|
||||
mainWebView = await createWebview(0, mainWindow, customSession);
|
||||
|
||||
if (!(await checkInternetConnectivity())) return mainWebView.webContents.loadFile(await findPath('nointernet.html'));
|
||||
|
||||
// load existing cookies
|
||||
logger.info('flushing cookies...');
|
||||
await flushCookies(customSession);
|
||||
logger.info('cookies flushed!');
|
||||
|
||||
// need to load the initial window
|
||||
await mainWebView.webContents.loadURL('https://start.duckduckgo.com');
|
||||
|
||||
// tab caching stuff
|
||||
const tabs = await getSavedTabs();
|
||||
if (Object.keys(tabs).length) {
|
||||
const r = await askUserQuestion(mainWindow, 'Restore History', `Would you like to restore ${Object.keys(tabs).length} previous tabs?`);
|
||||
if (r) loadTabs(customSession, tabs);
|
||||
}
|
||||
else {
|
||||
// mainWebView.webContents('https://duckduckgo.com/?t=h_&hps=1&start=1&q=hi&ia=web');
|
||||
mainWebView.webContents.loadURL('https://www.youtube.com/watch?v=aPO5JaShu2U', { userAgent: agent });
|
||||
// mainWebView.webContents.loadURL('https://www.youtube.com', { userAgent: agent });
|
||||
// mainWebView.webContents.loadURL('https://electronjs.org');
|
||||
mainWebView.webContents.setBackgroundThrottling(true);
|
||||
mainWindow.currentView = mainWebView;
|
||||
}
|
||||
|
||||
tabWebView.webContents.loadFile(await findPath('tabs.html'));
|
||||
// tabWebView.webContents.openDevTools({ mode: 'detach' });
|
||||
}
|
||||
|
||||
app.on('open-url', (e, webURL) => {
|
||||
e.preventDefault();
|
||||
console.log(`attempted to navigate to ${webURL}`);
|
||||
});
|
||||
|
||||
// listen for app ready event to create window
|
||||
app.whenReady().then(async () => {
|
||||
const customSession = session.fromPartition(partitionName);
|
||||
customSession.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
// handle third-party cookies
|
||||
if (permission === 'media' || permission === 'display-capture' || permission === 'notifications' || permission === 'fullscreen') {
|
||||
callback(true);
|
||||
}
|
||||
else callback(false);
|
||||
});
|
||||
|
||||
// session.defaultSession.webRequest.onBeforeRequest((details, cb) => {
|
||||
// if (details.url.startsWith('https://duckduckgo.com/l/?')) cb({ redirectURL: transformduckurl(details.url) });
|
||||
// else cb({ cancel: false });
|
||||
// })
|
||||
|
||||
customSession.protocol.handle('file', (r) => intercept(r, uid));
|
||||
customSession.protocol.handle('http', (r) => intercept(r, uid));
|
||||
customSession.protocol.handle('https', (r) => intercept(r, uid));
|
||||
|
||||
ipcinit(customSession);
|
||||
createWindow(customSession);
|
||||
|
||||
// for macOS: recreate a window if the dock icon is clicked and there are no open windows
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// app.on('browser-window-created', (_, window) => setUpShortcuts(uid, window));
|
||||
app.on('web-contents-created', async (e, contents) => {
|
||||
// contents.openDevTools({ mode: 'detach' });
|
||||
setUpShortcuts(uid);
|
||||
const u = await contents.executeJavaScript('window.location.href');
|
||||
if (contents.getType() === 'webview') handleWebViewInit(contents);
|
||||
});
|
||||
|
||||
// quit the app when all windows are closed (except on macOS)
|
||||
app.on('window-all-closed', () => {
|
||||
logger.info('all windows closed!');
|
||||
if (process.platform !== 'darwin') {
|
||||
quitRedis();
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
logger.info('shutting down...');
|
||||
const p = exec('node utils/clearCache.js --ischildproc 1>> logs/log.log 2>> logs/err.log');
|
||||
p.unref();
|
||||
|
||||
setTimeout(() => {
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}, 1500);
|
||||
|
||||
quitRedis();
|
||||
});
|
||||
|
||||
|
||||
app.commandLine.appendSwitch('ignore-gpu-blacklist');
|
||||
app.commandLine.appendSwitch('disable-gpu-compositing');
|
||||
@@ -0,0 +1,57 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
const tabClick = (tab, tabsContainer) => {
|
||||
tab.addEventListener('click', () => {
|
||||
if (tab.classList.contains('active')) return;
|
||||
|
||||
const tabs = tabsContainer.querySelectorAll('.tab');
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
|
||||
tab.classList.add('active');
|
||||
ipcRenderer.send('tab-open', Number(tab.dataset.tab));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function setup() {
|
||||
ipcRenderer.send('ping');
|
||||
|
||||
const tabsContainer = document.querySelector('.browser-tabs'),
|
||||
tabs = document.querySelectorAll('.tab'),
|
||||
addTabButton = document.querySelector('.add-tab');
|
||||
tabs.forEach(tab => tabClick(tab, tabsContainer));
|
||||
|
||||
addTabButton.addEventListener('click', () => {
|
||||
// create a new tab element
|
||||
const newTab = document.createElement('button');
|
||||
newTab.classList.add('tab');
|
||||
newTab.dataset.tab = `${tabsContainer.children.length}`;
|
||||
newTab.textContent = `Tab ${tabsContainer.children.length}`;
|
||||
tabsContainer.appendChild(newTab);
|
||||
newTab.scrollIntoView({ behavior: 'smooth' });
|
||||
|
||||
ipcRenderer.send('tab-new', tabsContainer.children.length - 1);
|
||||
|
||||
// add click event listener to the new tab
|
||||
newTab.addEventListener('click', () => tabClick(newTab, tabsContainer));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
process.once("loaded", () => {
|
||||
contextBridge.exposeInMainWorld('tabapi', {
|
||||
ping: () => ipcRenderer.send('ping'),
|
||||
click: () => ipcRenderer.invoke('tab-open'),
|
||||
close: () => ipcRenderer.invoke('tab-close'),
|
||||
newtab: () => ipcRenderer.invoke('tab-new')
|
||||
});
|
||||
|
||||
if (document.readyState === 'complete') setup();
|
||||
else document.addEventListener('DOMContentLoaded', () => setup());
|
||||
|
||||
|
||||
ipcRenderer.on('pong', () => console.info('the server replied with pong'));
|
||||
});
|
||||
|
||||
// document.addEventListener('click', () => ipcRenderer.send('ping'));
|
||||
Generated
+8049
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.13.12",
|
||||
"@gorhill/ubo-core": "^0.1.30",
|
||||
"cron": "^3.1.7",
|
||||
"dompurify": "^3.1.7",
|
||||
"electron-oauth-helper": "^5.1.1",
|
||||
"electron-oauth2": "^3.0.0",
|
||||
"express": "^4.21.1",
|
||||
"googleapis": "^144.0.0",
|
||||
"jquery": "^3.7.1",
|
||||
"jsdom": "^25.0.1",
|
||||
"mhtml2html": "^3.0.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"open": "^10.1.0",
|
||||
"redis": "^4.7.0",
|
||||
"winston": "^3.15.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "ion.browser",
|
||||
"linux": {}
|
||||
},
|
||||
"preload": {
|
||||
"js": "./JS/preload.cjs"
|
||||
},
|
||||
"name": "ion-browser",
|
||||
"version": "1.0.0",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron . --trace-warnings --no-sandbox --load-extension",
|
||||
"pack": "electron-builder --dir",
|
||||
"dist": "electron-builder"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "the ion browser!",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"electron": "^33.0.1",
|
||||
"electron-builder": "^25.1.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Agent, CredentialSession } from '@atproto/api';
|
||||
import { CronJob } from 'cron';
|
||||
import json from '../secrets/config.json' with { type: 'json' };
|
||||
const { uname, upass } = json.bluesky;
|
||||
|
||||
// Create a Bluesky Agent
|
||||
const session = new CredentialSession(new URL('https://bsky.social'));
|
||||
|
||||
const agent = new Agent(session);
|
||||
|
||||
async function main() {
|
||||
await session.login({ identifier: uname, password: upass });
|
||||
const { data: accountData } = await agent.getProfile({ actor: session.did });
|
||||
console.log(accountData);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -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 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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