From 014e4c8b7b84ed496e54e172cc546aa72ceaec8d Mon Sep 17 00:00:00 2001 From: ION606 Date: Mon, 4 Nov 2024 11:55:14 -0500 Subject: [PATCH] added permissions for website --- CSS/tabs.css | 12 ++++ HTML/permspopup.html | 147 ++++++++++++++++++++++++++++++++++++++++ HTML/tabs.html | 3 +- JS/clientAdBlock.js | 71 ++++++++++++++++--- JS/optimize.js | 2 +- JS/permspopup.cjs | 37 ++++++++++ JS/preload.cjs | 5 +- main.js | 42 +++++++++--- organization/tabs.cjs | 6 +- package.json | 2 + serverJS/adblock.js | 45 +++++++----- serverJS/history.cjs | 28 +++++++- serverJS/imports.js | 10 ++- serverJS/intercept.js | 23 +++---- serverJS/shortcuts.js | 1 + utils/args.js | 16 +++++ utils/dialogue.js | 71 ++++++++++++++++++- utils/ipc.js | 25 ++++--- utils/webviewHelpers.js | 32 +++++++++ 19 files changed, 507 insertions(+), 71 deletions(-) create mode 100644 HTML/permspopup.html create mode 100644 JS/permspopup.cjs create mode 100644 utils/args.js diff --git a/CSS/tabs.css b/CSS/tabs.css index 3874934..b8224b9 100644 --- a/CSS/tabs.css +++ b/CSS/tabs.css @@ -49,6 +49,7 @@ html { align-items: center; margin-left: auto; transition: background-color 0.3s, color 0.3s; + margin-right: 5px; } .add-tab:hover { @@ -58,4 +59,15 @@ html { button { cursor: pointer; +} + +#settingsbtn { + background-color: transparent; + color: black; + border-radius: 50%; + padding: 7px; +} + +#settingsbtn:hover { + background-color: #3b3b5a46; } \ No newline at end of file diff --git a/HTML/permspopup.html b/HTML/permspopup.html new file mode 100644 index 0000000..ebbd44a --- /dev/null +++ b/HTML/permspopup.html @@ -0,0 +1,147 @@ + + + + + + + Site Permissions + + + + +
+
Saving...
+
+ +

Permissions for

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/HTML/tabs.html b/HTML/tabs.html index 5d8e098..1e1bae6 100644 --- a/HTML/tabs.html +++ b/HTML/tabs.html @@ -13,7 +13,8 @@
- + + diff --git a/JS/clientAdBlock.js b/JS/clientAdBlock.js index 3d75c9b..8c5b01f 100644 --- a/JS/clientAdBlock.js +++ b/JS/clientAdBlock.js @@ -1,15 +1,70 @@ -// save the original window.open function -const originalWindowOpen = window.open; - -// override the window.open function -window.open = function (url, target, features) { +function checkAdBlock(url, target, features, originalWindowOpen) { 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); -}; \ No newline at end of file +} + + +// needs to be inside the function or it'll get assigned twice +async function setupAdBlock(perms) { + // attach listeners to the document body for each event type + ['click', 'mousedown', 'mouseup', 'dblclick', 'keydown', 'keyup', 'submit'].forEach((eventType) => { + document.body.addEventListener(eventType, (e) => { + console.log(`Event ${e.type} triggered by:`, e.target); + }, true); // use capture phase + }); + + const originalWindowOpen = window.open, + isValidURL = (u) => { try { return new URL(u); } catch (_) { return false; } } + + window.open = (url, target, features) => { + if (!perms['popup']) return window.electronAPI.promptperms(); + + checkAdBlock(url, target, features, originalWindowOpen); + } + + const linkels = Array.from(document.links), + imgs = Array.from(document.querySelectorAll('img')), + allLinks = new Set(linkels.map(el => el.href).filter(isValidURL)), + imgsrcs = new Set(imgs.map(o => o.src)); + + await fetch(`https://ion-adblock.${window.location.hostname}`, { + body: JSON.stringify([...allLinks]), //['https://pagead2.googlesyndication.com/tag/js/gpt.js'] + headers: { 'Content-Type': 'application/json' }, + method: 'POST' + }) + .then(r => r.json()).then(rj => { + const resjson = rj.filter(el => el[1]), + obj = Object.fromEntries(resjson); + + linkels.forEach(async (el) => { + if (obj[el.href]) el.remove(); + }); + }); + + + await fetch(`https://ion-adblock.${window.location.hostname}`, { + body: JSON.stringify([...imgsrcs]), //['https://pagead2.googlesyndication.com/tag/js/gpt.js'] + headers: { 'Content-Type': 'application/json' }, + method: 'POST' + }) + .then(r => r.json()).then(rj => { + const resjson = rj.filter(el => el[1]), + obj = Object.fromEntries(resjson); + + console.log(obj); + imgs.forEach(async (el) => { + if (obj[el.src]) el.remove(); + }); + }); + + sessionStorage.setItem('ran-adblock', 1); + console.log('adblock injected!'); +} + +// if (document.readyState === 'complete' && !sessionStorage.getItem('ran-adblock')) setupAdBlock(); +// else window.addEventListener('DOMContentLoaded', setupAdBlock); \ No newline at end of file diff --git a/JS/optimize.js b/JS/optimize.js index b955b51..674317d 100644 --- a/JS/optimize.js +++ b/JS/optimize.js @@ -61,4 +61,4 @@ function optimize() { } if (document.readyState === 'complete' && !sessionStorage.getItem('ran-optimize')) optimize(); - +else window.addEventListener('DOMContentLoaded', optimize); \ No newline at end of file diff --git a/JS/permspopup.cjs b/JS/permspopup.cjs new file mode 100644 index 0000000..d58af8a --- /dev/null +++ b/JS/permspopup.cjs @@ -0,0 +1,37 @@ +const { ipcRenderer } = require("electron"); + +ipcRenderer.on('conf', (e, id) => { + document.querySelector(id).style.borderColor = 'green'; +}) + +window.onbeforeunload = () => window.close(); + +document.addEventListener('DOMContentLoaded', () => { + const title = new URLSearchParams(window.location.search).get('origin'); + document.querySelector('#sitename').textContent = title; + + ipcRenderer.send('get-site-perms', title); + ipcRenderer.on('site-perms', (e, permsRaw) => { + const perms = JSON.parse(permsRaw); + if (!perms) return ipcRenderer.send('set-site-perms-all', title, 'ask'); + + document.querySelector('#loading').style.display = 'none'; + + for (const key in perms) { + const el = document.querySelector(`#${key}`); + if (el.value === perms[key]) el.style.border = 'solid green 1px'; + el.value = perms[key]; + } + }); + + document.querySelectorAll('select').forEach((el) => { + el.addEventListener('change', (e) => { + e.preventDefault(); + e.target.style.border = 'none'; + document.querySelector('#loading').style.display = 'flex'; + const { id, value } = e.target; + ipcRenderer.send('set-site-perms', title, id, value); + }); + }); + +}); \ No newline at end of file diff --git a/JS/preload.cjs b/JS/preload.cjs index 9794116..e20bf03 100644 --- a/JS/preload.cjs +++ b/JS/preload.cjs @@ -71,7 +71,8 @@ contextBridge.exposeInMainWorld('electronAPI', { onReceive: (channel, func) => { ipcRenderer.on(channel, (event, ...args) => func(...args)); }, - checkperms: (sitehostname) => ipcRenderer.send('get-site-perms', sitehostname) + checkperms: (sitehostname) => ipcRenderer.send('get-site-perms', sitehostname), + promptperms: () => ipcRenderer.send('prompt-terms', window.location.hostname) }); // ipcRenderer.on('tab-opened', (ev, id) => { @@ -87,7 +88,7 @@ contextBridge.exposeInMainWorld('tabAPI', { addTab: (url) => ipcRenderer.send('add-tab', url || 'about:blank') }); -const load = () => { +const load = async () => { console.info("PRELOAD LOADED!"); if (window.location.origin === 'lite.duckduckgo.com') { diff --git a/main.js b/main.js index a4a0ae7..d929ec5 100644 --- a/main.js +++ b/main.js @@ -3,12 +3,15 @@ import { app, BaseWindow, BrowserWindow, session, pushNotifications } from 'elec import { exec } from 'child_process'; import path from 'path'; import { - logger, intercept, setUpShortcuts, organizeTabIds, + logger, intercept, setUpShortcuts, organizeTabIds, isValidURL, getSavedTabs, loadTabs, flushCookies, askUserQuestion, ipcinit, checkInternetConnectivity, findPath, createWebview, - handleWebViewInit, setupRedis, quitRedis + handleWebViewInit, setupRedis, quitRedis, redisclient, blocked } from './serverJS/imports.js'; +import { getSitePerms, promptForPerms } from './utils/dialogue.js'; +import { getCurrentWindow } from './serverJS/tabs_server.js'; +// await (await import('./utils/args.js')).handleArgs(); await setupRedis(); export const uid = 1, @@ -60,8 +63,9 @@ async function createWindow(customSession) { 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://hianime.to'); + // mainWebView.webContents.loadURL('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); @@ -80,12 +84,25 @@ app.on('open-url', (e, 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); + customSession.setPermissionRequestHandler(async (webContents, permission, callback) => { + const origin = await webContents.executeJavaScript('window.location.origin'); + if (!origin) return callback(false); + + const permsRaw = await getSitePerms(null, origin); + if (!permsRaw) { + await promptForPerms(getCurrentWindow(), origin); + return callback(false); } - else callback(false); + + const permsJSON = JSON.parse(permsRaw); + if (permission in permsJSON && permsJSON[permission] === 'ask') { + const res = await askUserQuestion(getCurrentWindow(), 'safety prompt', `allow ${origin} to access ${permission}?`); + return callback(res); + } + + // handle third-party cookies + if (permission === 'media' || permission === 'fullscreen') callback(true); + else callback(permission in permsJSON && permsJSON[permission] === 'allow'); }); // session.defaultSession.webRequest.onBeforeRequest((details, cb) => { @@ -139,6 +156,13 @@ app.on('before-quit', async (e) => { quitRedis(); }); +app.on('browser-window-created', async (ev, win) => { + const newURL = await win.webContents.executeJavaScript('window.location.href'), + origin = await win.webContents.executeJavaScript('window.location.origin'); + + console.log(newURL, win.webContents?.opener?.url, blocked(newURL, win.webContents?.opener?.url), blocked(newURL)); + if (blocked(newURL, win.webContents?.opener?.url)) win.close(); +}); app.commandLine.appendSwitch('ignore-gpu-blacklist'); app.commandLine.appendSwitch('disable-gpu-compositing'); diff --git a/organization/tabs.cjs b/organization/tabs.cjs index 5ffefbb..67e57e8 100644 --- a/organization/tabs.cjs +++ b/organization/tabs.cjs @@ -18,7 +18,9 @@ function setup() { const tabsContainer = document.querySelector('.browser-tabs'), tabs = document.querySelectorAll('.tab'), - addTabButton = document.querySelector('.add-tab'); + addTabButton = document.querySelector('#addtabbtn'), + settingsbtn = document.querySelector('#settingsbtn'); + tabs.forEach(tab => tabClick(tab, tabsContainer)); addTabButton.addEventListener('click', () => { @@ -35,6 +37,8 @@ function setup() { // add click event listener to the new tab newTab.addEventListener('click', () => tabClick(newTab, tabsContainer)); }); + + settingsbtn.addEventListener('click', () => ipcRenderer.send('open-settings')) } diff --git a/package.json b/package.json index 6f7f64a..0b6d3c7 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "main": "main.js", "scripts": { "start": "electron . --trace-warnings --no-sandbox --load-extension", + "clearcache": "electron . --trace-warnings --no-sandbox --load-extension --clear-cache", + "nocache": "electron . --trace-warnings --no-sandbox --load-extension --no-cache", "pack": "electron-builder --dir", "dist": "electron-builder" }, diff --git a/serverJS/adblock.js b/serverJS/adblock.js index ce208b5..2f520d5 100644 --- a/serverJS/adblock.js +++ b/serverJS/adblock.js @@ -1,8 +1,9 @@ import { StaticNetFilteringEngine } from '@gorhill/ubo-core'; -import fs from 'fs/promises'; +import fsPromise from 'fs/promises'; import { checkInternetConnectivity } from '../utils/misc.js'; import loggermod from '../utils/logger.cjs'; -const { logger } = loggermod; +const { logger } = loggermod, + adblockcachepath = 'cache/adblock'; const blocklists = [ @@ -26,33 +27,42 @@ const blocklists = [ async function fetchList(url) { - return fetch(url).then(r => { - return r.text(); - }).then(raw => { - return { raw }; - }).catch(reason => { - logger.error(reason); - }); + return fetch(url) + .then(r => r.text()) + .then(raw => ({ 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 apiUrl = `https://api.github.com/repos/uBlockOrigin/uAssets/contents/filters`; +const getFilesFromDirectory = async () => { + try { + const response = await fetch(apiUrl); + const data = await response.json(); + + // check if the response data is an array (list of files) + if (Array.isArray(data)) return data.filter(file => file.type === 'file').map(o => o.download_url); + else console.log('No files found or the directory path is incorrect.'); + } catch (error) { + console.error('Error fetching files:', error.message); + } +}; const pathToSelfie = 'cache/selfie.txt'; +if (!(await import('fs')).existsSync(adblockcachepath)) await fsPromise.mkdir(adblockcachepath); // Up to date serialization data (aka selfie) available? let selfie; -const ageInDays = await fs.stat(pathToSelfie).then(stat => { +const ageInDays = await fsPromise.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' }) + selfie = await fsPromise.readFile(pathToSelfie, { encoding: 'utf8' }) .then(data => typeof data === 'string' && data !== '' && data) .catch(() => { }); if (typeof selfie === 'string') { @@ -63,11 +73,14 @@ if (ageInDays <= 7) { // 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 totalLists = new Set(blocklists.concat(...(await getFilesFromDirectory()))); + await snfe.useLists([...totalLists].map(fetchList).filter(o => o)); + + logger.info(`using ${totalLists.size} adblock lists`) const selfie = await snfe.serialize(); - fs.mkdir('cache', { recursive: true }); - await fs.writeFile(pathToSelfie, selfie); + fsPromise.mkdir('cache', { recursive: true }); + await fsPromise.writeFile(pathToSelfie, selfie); } diff --git a/serverJS/history.cjs b/serverJS/history.cjs index dd7ad08..ecd3899 100644 --- a/serverJS/history.cjs +++ b/serverJS/history.cjs @@ -52,9 +52,33 @@ async function setupRedis() { .connect(); // clear history on browser boot - client.flushDb(); + let cursor = 0; + + do { + const result = await client.scan(cursor, { MATCH: `searchHistory:*`, COUNT: 100 }); + cursor = result.cursor || 0; + const keys = result.keys; + if (keys.length > 0) await client.del(...keys); + } while (cursor !== 0); + + // await client.flushDb(); logger.info('Redis Client Connected!'); } -module.exports = { setupRedis, redisclient: client, getHistory, addHistory, displayHistory, quitRedis }; + +/** + * + * @returns {Promise} + */ +const getClient = () => { + return new Promise(resolve => { + const intid = setInterval(() => { + if (!client) return; + resolve(client); + clearInterval(intid); + }, 100); + }) +} + +module.exports = { setupRedis, redisclient: getClient, getHistory, addHistory, displayHistory, quitRedis }; diff --git a/serverJS/imports.js b/serverJS/imports.js index 9f4b1c1..cb1f83d 100644 --- a/serverJS/imports.js +++ b/serverJS/imports.js @@ -6,11 +6,12 @@ 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 { checkInternetConnectivity, isValidURL } from '../utils/misc.js'; import { findPath } from '../utils/paths.js'; import { createWebview, handleWebViewInit } from '../utils/webviewHelpers.js'; +import blocked from './adblock.js'; -const { setupRedis, quitRedis } = await import('../serverJS/history.cjs'); +const { setupRedis, quitRedis, redisclient } = await import('../serverJS/history.cjs'); const { logger } = loggermod; export { @@ -28,5 +29,8 @@ export { createWebview, handleWebViewInit, setupRedis, - quitRedis + quitRedis, + redisclient, + blocked, + isValidURL }; diff --git a/serverJS/intercept.js b/serverJS/intercept.js index a2db722..7330ce2 100644 --- a/serverJS/intercept.js +++ b/serverJS/intercept.js @@ -31,7 +31,14 @@ export default async function intercept(request, uid) { try { const u = new URL(request.url); - if (u.protocol === 'file:' || u.hostname.startsWith('ion-local')) { + if (u.hostname.startsWith('ion-adblock')) { + const links = await request.json(); + console.log(links); + + const r = links.map(l => ([l, blocked(l)])); + return new Response(JSON.stringify(r)); + } + else 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 }); @@ -117,20 +124,6 @@ export default async function intercept(request, uid) { } 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) { diff --git a/serverJS/shortcuts.js b/serverJS/shortcuts.js index 042b9df..e54f5f4 100644 --- a/serverJS/shortcuts.js +++ b/serverJS/shortcuts.js @@ -15,6 +15,7 @@ export default async function setUpShortcuts(uid) { // getCurrentWindow().isFocused() ? getCurrentTab()?.toggleDevTools() : null }); globalShortcut.register('Control+H', () => getCurrentTab()?.webContents.executeJavaScript('window.electronAPI.displayHistory()')); + globalShortcut.register('Control+P', () => getCurrentTab()?.webContents.print()); // zoom globalShortcut.register('Control+=', () => changeZoom(getCurrentTab(), true)); diff --git a/utils/args.js b/utils/args.js new file mode 100644 index 0000000..ccefd66 --- /dev/null +++ b/utils/args.js @@ -0,0 +1,16 @@ +import fs from 'fs'; +import { finalTabCleanup } from "./clearCache.js"; + +export async function handleArgs() { + console.log(process.argv); + for (const arg of process.argv) { + console.log(arg); + if (arg === '--clear-cache') { + await finalTabCleanup(); + fs.rmSync(`${import.meta.dirname}/cache`, { recursive: true }); + } + else if (arg === '--no-cache') { + console.error('TODO: RUN WITH NEW SESSION/PARTITION'); + } + } +} \ No newline at end of file diff --git a/utils/dialogue.js b/utils/dialogue.js index 6ed3744..1cd3e44 100644 --- a/utils/dialogue.js +++ b/utils/dialogue.js @@ -1,4 +1,8 @@ -import { dialog } from 'electron'; +import { BrowserWindow, dialog } from 'electron'; +import { findPath } from './paths.js'; +import { redisclient } from '../serverJS/history.cjs'; + +const preloadpath = await findPath('permspopup.cjs', true); export async function askUserQuestion(window, title, question) { const response = await dialog.showMessageBox(window, { @@ -8,6 +12,67 @@ export async function askUserQuestion(window, title, question) { title, message: question, }); - + return response.response === 0; // true if 'Yes' was clicked, false if 'No' -} \ No newline at end of file +} + + + +const perms = ["geolocation", "camera", "microphone", "notifications", "popups"]; + +export async function promptForPerms(window, origin) { + return new Promise(async (resolve) => { + const w = new BrowserWindow({ + height: 600, + width: 400, + alwaysOnTop: true, + autoHideMenuBar: true, + darkTheme: true, + modal: true, + parent: window, + webPreferences: { + contextIsolation: true, + javascript: true, + allowRunningInsecureContent: false, + nodeIntegration: true, + preload: preloadpath + } + }); + + await w.loadFile(await findPath('permspopup.html'), { + search: `origin=${origin}` + }); + w.show(); + w.on('close', resolve); + }); +} + +/** + * @param {Electron.IpcMainEvent} e + * @param {String} sitehostname + * @param {String} id + * @param {String} value + */ +export async function setSitePerms(e, sitehostname, id, value = 'ask', all = false) { + const client = await redisclient(), + dataRaw = await client.get(`perms-${sitehostname}`), + data = dataRaw ? JSON.parse(dataRaw) : null; + + if (all || !data) await client.set(`perms-${sitehostname}`, JSON.stringify(Object.fromEntries(perms.map(p => [p, value])))); + else { + data[id] = value; + await client.set(`perms-${sitehostname}`, JSON.stringify(data)); + } + + if (e?.sender) e.sender.send('site-perms', JSON.stringify(data)); +} + +/** + * @param {Electron.IpcMainEvent} e + * @param {String} sitehostname + */ +export async function getSitePerms(e, sitehostname) { + const client = await redisclient(); + if (!e) return await client.get(`perms-${sitehostname}`); + else e.sender.send('site-perms', await client.get(`perms-${sitehostname}`)); +} diff --git a/utils/ipc.js b/utils/ipc.js index fe95cc4..bae75d9 100644 --- a/utils/ipc.js +++ b/utils/ipc.js @@ -4,6 +4,7 @@ import fs from 'fs'; import { findPath } from './paths.js'; import * as tabModule from '../serverJS/tabs_server.js'; import loggermod from '../utils/logger.cjs'; +import { getSitePerms, promptForPerms, setSitePerms } from './dialogue.js'; const { logger } = loggermod; @@ -24,17 +25,19 @@ export default function init(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 }); - }); + ipcMain.on('set-site-perms', (e, sitehostname, id, value) => setSitePerms(e, sitehostname, id, value)); + ipcMain.on('set-site-perms-all', (e, sitehostname, id, value) => setSitePerms(e, sitehostname, id, value, true)); + ipcMain.on('get-site-perms', getSitePerms); + ipcMain.on('prompt-terms', (e, sitehostname) => promptForPerms(tabModule.getCurrentWindow(), sitehostname)); + + // TODO: make an actual settings page + ipcMain.on('open-settings', async (e) => promptForPerms(tabModule.getCurrentWindow(), await tabModule.getCurrentTab()?.webContents.executeJavaScript('window.location.hostname'))); } const renderer = (fs.readFileSync(await findPath('renderer.js'), 'utf-8')), - optimize = (fs.readFileSync(await findPath('optimize.js'), 'utf-8')); + optimize = (fs.readFileSync(await findPath('optimize.js'), 'utf-8')), + adblock = (fs.readFileSync(await findPath('clientAdBlock.js'), 'utf-8')) /** @@ -42,9 +45,11 @@ const renderer = (fs.readFileSync(await findPath('renderer.js'), 'utf-8')), */ 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); + await mainWindow.webContents.executeJavaScript(renderer); + await mainWindow.webContents.executeJavaScript(optimize); + await mainWindow.webContents.executeJavaScript(adblock); + const perms = (await getSitePerms(null, mainWindow.webContents.getURL())) || {}; + await mainWindow.webContents.executeJavaScript(`setupAdBlock(${JSON.stringify(perms)})`); const title = await mainWindow.webContents.executeJavaScript('document.title'); addHistory(uid, mainWindow.webContents.getURL(), 200, title); diff --git a/utils/webviewHelpers.js b/utils/webviewHelpers.js index eb0ca4b..0ba4bef 100644 --- a/utils/webviewHelpers.js +++ b/utils/webviewHelpers.js @@ -5,6 +5,8 @@ import { addEl, isValidURL } from "./misc.js"; import { startinject } from "./ipc.js"; import intercept, { noworker } from "../serverJS/intercept.js"; import loggermod from '../utils/logger.cjs'; +import { askUserQuestion, getSitePerms, promptForPerms, setSitePerms } from "./dialogue.js"; +import { getCurrentWindow } from "../serverJS/tabs_server.js"; const { logger } = loggermod; @@ -66,6 +68,36 @@ export async function createWebview(tabId, currentWindow, customSession, preload startinject(view, uid); }); + let perms; + + const refreshperms = async (_, u) => { + perms = await getSitePerms(null, isValidURL(u)?.hostname); + } + view.webContents.on('did-navigate', refreshperms); + + view.webContents.setWindowOpenHandler((details) => { + // let run out for an automatic "deny" + const hostname = isValidURL(view.webContents.getURL())?.hostname; + + if (!perms) promptForPerms(getCurrentWindow(), hostname).then(() => refreshperms(null, hostname)); + // else if (perms['popups'] === 'ask') askUserQuestion(view, 'allow popups', `allow popups from ${hostname}`) + else { + const permsJSON = JSON.parse(perms); + + if (permsJSON['popups'] === 'ask') { + askUserQuestion(getCurrentWindow(), 'safety prompt', `allow ${hostname} to open popups?`) + .then(allowed => setSitePerms(null, hostname, 'popups', allowed ? 'allow' : 'deny')) + .catch(console.error) + .finally(() => refreshperms(null, hostname)); + } + if (permsJSON['popups'] === 'allow') return { action: 'allow' }; + } + + // fall through + return { action: 'deny' }; + }); + + view.webContents.setBackgroundThrottling(true); resizeWebView(); return view;