Compare commits
84 commits
dependabot
...
master
Author | SHA1 | Date | |
---|---|---|---|
be64967438 | |||
154d15188d | |||
71b407c211 | |||
f11841c1a2 | |||
11b7692d14 | |||
6825c0d40c | |||
ff3f0d77cb | |||
2ebdd73942 | |||
87b53acf7e | |||
55a4b89b24 | |||
fc736da1d3 | |||
1c1f7934fb | |||
4bac2b0542 | |||
bad5e26a86 | |||
42de741e08 | |||
7198b136a6 | |||
8d18437dfa | |||
37e109f739 | |||
dc9f6a45a6 | |||
995962a497 | |||
fbdeb5dc7b | |||
5789eb88a2 | |||
5dada52da5 | |||
d16aa9b3f3 | |||
![]() |
c0037f256e | ||
![]() |
6531460cd0 | ||
![]() |
95219d7298 | ||
![]() |
c7159725e2 | ||
![]() |
b4e88f8b22 | ||
![]() |
ee706b339f | ||
![]() |
726d22f096 | ||
![]() |
f244e0f003 | ||
![]() |
010e954d7d | ||
![]() |
38209621b9 | ||
![]() |
1bef1f1615 | ||
![]() |
1549b64317 | ||
![]() |
e4776f36ad | ||
![]() |
2f64e8b41f | ||
![]() |
b483e91f1e | ||
![]() |
8b6a491ed5 | ||
![]() |
252984a504 | ||
![]() |
dda48fc695 | ||
![]() |
d1c7f53d16 | ||
![]() |
981d139755 | ||
![]() |
d484a3557b | ||
![]() |
14fa018ec1 | ||
![]() |
6d93ff50eb | ||
![]() |
bfd42f9fa7 | ||
![]() |
357ad09eaa | ||
![]() |
f70f63e41c | ||
![]() |
3f43c17b9e | ||
![]() |
5ef8a09ff0 | ||
![]() |
1eee3a31f8 | ||
![]() |
1b6b848cef | ||
![]() |
f36b1ae72a | ||
![]() |
3b07296b75 | ||
![]() |
8a2bf6c631 | ||
![]() |
3a10c82fb3 | ||
![]() |
e9c68dcf9f | ||
![]() |
c6afbb7d6f | ||
![]() |
842f9bf8c5 | ||
![]() |
cbc14de39d | ||
![]() |
afd1928f02 | ||
![]() |
1cdea46b28 | ||
![]() |
434db9cd0f | ||
![]() |
af7b08ce56 | ||
![]() |
44580739d2 | ||
![]() |
d5dedb091c | ||
![]() |
5439fadf09 | ||
![]() |
0c6344b8e0 | ||
![]() |
cd0c61e813 | ||
![]() |
039b5b14c8 | ||
![]() |
84f489be00 | ||
![]() |
379052e5d2 | ||
![]() |
88f3a44242 | ||
![]() |
187c68e703 | ||
![]() |
d8aebf9372 | ||
![]() |
9337ec75ac | ||
![]() |
b664e3f3f1 | ||
![]() |
c57bcb26f9 | ||
![]() |
84b29939b7 | ||
![]() |
22c36901ee | ||
![]() |
f3b089768f | ||
![]() |
d750fef706 |
25 changed files with 8859 additions and 189 deletions
.DS_Store
.cursor/rules
.gitignoreLICENSEREADME.mdad-blocker.jscrx-extractor.jsdownload.pngextension-manager.jsextensions-preload.jsextensions.htmlindex.htmlitworks.htmllibbrowz.jslogo.pngmain.jspackage-lock.jsonpackage.jsonpreload.jsrenderer.jssneedium-maker-mac.shsneedium-maker.shstyles.cssuserscript
window-manager.js
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
19
.cursor/rules/basic-ruleset.mdc
Normal file
19
.cursor/rules/basic-ruleset.mdc
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
description: A very basic ruleset
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
# Basic rules
|
||||||
|
|
||||||
|
- Always use functional code
|
||||||
|
- Always prefer simple, easy-to-understand code
|
||||||
|
- Always create a .gitignore file with the build directory in it.
|
||||||
|
- Always code what the user specifies and that of which it is obvious that its within the user's specifications
|
||||||
|
- If not sure about something, ask the user
|
||||||
|
- Keep .gitignore up to date
|
||||||
|
- Comment code well
|
||||||
|
- NEVER EVER EDIT .env files
|
||||||
|
- Don't change config files unless needed
|
||||||
|
- If function line count is above 55, split into multiple smaller functions or refactor
|
||||||
|
- If file line count above 400 split into more files or refactor
|
||||||
|
- Feel free to auto-apply any changes that follow these or any other rules.
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1 +1,13 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
seedium-linux-x64
|
||||||
|
sneedium-win32-x64
|
||||||
|
seedium-linux-arm64
|
||||||
|
sneedium-win32-arm64
|
||||||
|
sneedium-darwin-x64
|
||||||
|
sneedium-darwin-arm64
|
||||||
|
out/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
*.log
|
|
@ -1,6 +1,6 @@
|
||||||
The Samuel Public License Revision 5 (SPL-R5)
|
The Sammy Public License Revision 5 Sub-Revision 2 (SPL-R5 SR2)
|
||||||
|
|
||||||
Copyright (c) 2024 Maxwell Drake
|
Copyright (c) Sneed Group
|
||||||
|
|
||||||
This document grants permission, without charge, to any individual acquiring a copy of the software and its associated documentation files (hereinafter referred to as the "Software"). Such individuals are authorized to engage in activities related to the Software with certain restrictions (listed below), including, but not limited to, the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software. These permissions extend to persons to whom the Software is furnished, subject to compliance with the specified conditions outlined below.
|
This document grants permission, without charge, to any individual acquiring a copy of the software and its associated documentation files (hereinafter referred to as the "Software"). Such individuals are authorized to engage in activities related to the Software with certain restrictions (listed below), including, but not limited to, the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software. These permissions extend to persons to whom the Software is furnished, subject to compliance with the specified conditions outlined below.
|
||||||
|
|
||||||
|
@ -10,10 +10,20 @@ In making contributions to the Software, contributors irrevocably assign, transf
|
||||||
|
|
||||||
Furthermore, this document permits the reuse and redistribution of both executable binaries and source code, contingent upon the inclusion of the previously mentioned copyright notice and permission notice in all copies or substantial portions of the Software. It is imperative that you explicitly acknowledge and agree that the owner(s) retain ownership rights over the aforementioned source code.
|
Furthermore, this document permits the reuse and redistribution of both executable binaries and source code, contingent upon the inclusion of the previously mentioned copyright notice and permission notice in all copies or substantial portions of the Software. It is imperative that you explicitly acknowledge and agree that the owner(s) retain ownership rights over the aforementioned source code.
|
||||||
|
|
||||||
Moreover, companies using the Software are encouraged to contribute upstream. Fortune 500 companies are required to make an annual contribution of at least 20,000 USD or an equivalent amount to support the project's sustainability unless no donation option is provided.
|
Additionally, note that the use of AI-assisted tools, including but not limited to GitHub Copilot and ChatGPT, is expressly permitted in conjunction with this software. Users are encouraged to leverage these AI tools to enhance their experience in developing, modifying, and interacting with the Software. The permission granted herein extends to the integration and utilization of AI-generated content for coding and communication purposes. The owner(s) of the Software acknowledge and embrace the collaborative nature of AI-assisted development.
|
||||||
|
|
||||||
Additionally, note that the use of AI-assisted tools, including but not limited to GitHub Copilot and ChatGPT, is expressly permitted in conjunction with this software. Users are encouraged to leverage these AI tools to enhance their experience in developing, modifying, and interacting with the Software. The permission granted herein extends to the integration and utilization of AI-generated content for coding and communication purposes. The owners(s) of the Software acknowledge and embrace the collaborative nature of AI-assisted development.
|
In addition, the owner of the code is granted the authority to update their copy of The Sammy Public License (SPL) to the latest revision. This update may be undertaken at the discretion of the owner to ensure alignment with any subsequent revisions made to The Sammy Public License.
|
||||||
|
|
||||||
In addition, the owner of the code is granted the authority to update their copy of The Samuel Public License (SPL) to the latest revision. This update may be undertaken at the discretion of the owner to ensure alignment with any subsequent revisions made to The Samuel Public License.
|
Minimum Payment Requirements by Business Size:
|
||||||
|
|
||||||
|
1. Fortune 500 Companies: Companies that are included in the Fortune 500 list are required to make an annual contribution of at least 42,069 USD or an equivalent amount per project used to support the sustainability of the Software, unless no donation option is provided. Additionally, Fortune 500 companies are required to contribute their changes upstream to the Software.
|
||||||
|
|
||||||
|
2. Large Non-Fortune 500 Companies: Non-Fortune 500 large companies (defined as companies with 128 or more employees) must pay a minimum of 15,000 USD per year for the continued use of the Software.
|
||||||
|
|
||||||
|
3. Medium Businesses: Medium businesses (defined as companies with 42 to 128 employees) are required to pay a minimum of 420 USD per year for the continued use of the Software.
|
||||||
|
|
||||||
|
4. Small Businesses: Small businesses (defined as companies with 42 or fewer employees) are required to pay a minimum of 69 USD per year for the continued use of the Software.
|
||||||
|
|
||||||
|
These fees are intended to ensure the sustainability of the project and may be subject to change by the project owner(s). Payment is due annually, and the corresponding amount must be paid no later than 30 days after the beginning of each calendar year. Failure to comply with these payment terms may result in revocation of the right to use the Software.
|
||||||
|
|
||||||
The aforementioned copyright notice and this permission notice must be included in all copies or substantial portions of the Software.
|
The aforementioned copyright notice and this permission notice must be included in all copies or substantial portions of the Software.
|
42
README.md
42
README.md
|
@ -1,7 +1,22 @@
|
||||||
<img src="logo.png" align="right" width="15%"/>
|
<img src="https://github.com/Sneed-Group/sneedium/blob/master/logo.png?raw=true" align="right" width="15%"/>
|
||||||
|
|
||||||
# F-Stopium
|
# Sneedium
|
||||||
A basic web browser in Electron. ***Now with our own, functioning adblocker and privacy redirection technologies!***
|
A basic web browser in Electron. ***With a functioning adblocker, privacy redirection technologies, and Chrome extension support!***
|
||||||
|
|
||||||
|
[](https://sneedgit.nodemixaholic.com/Sneed-Group/sneedium/releases)
|
||||||
|
|
||||||
|
## Testing, compiling, and creation notes
|
||||||
|
|
||||||
|
To clone and run this repository you'll need [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
# Run the app
|
||||||
|
npm start
|
||||||
|
# Run in development mode (with dev tools)
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
Supports compiling via electron packager. Install and run it with:
|
Supports compiling via electron packager. Install and run it with:
|
||||||
|
|
||||||
|
@ -19,26 +34,25 @@ A basic Electron application needs just these files:
|
||||||
- `index.html` - A web page to render. This is the app's **renderer process**.
|
- `index.html` - A web page to render. This is the app's **renderer process**.
|
||||||
- `preload.js` - A content script that runs before the renderer process loads.
|
- `preload.js` - A content script that runs before the renderer process loads.
|
||||||
|
|
||||||
|
## Chrome Extension Support
|
||||||
|
|
||||||
## To Use
|
Sneedium now supports loading and using Chrome extensions. Extensions are managed through a dedicated Extension Manager interface:
|
||||||
|
|
||||||
To clone and run this repository you'll need [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line:
|
1. Click the extension button (🧩) in the browser toolbar to open the extension manager
|
||||||
|
2. Install extensions by clicking the "Install Extension" button and selecting a .crx file
|
||||||
|
3. Extensions are stored in the `SneedExtensions` folder in your home directory
|
||||||
|
4. You can also manually install extensions by unpacking them into subdirectories of the `SneedExtensions` folder
|
||||||
|
|
||||||
```bash
|
Note: Not all Chrome extensions may work perfectly due to API differences between Chrome and Electron. Features like sync and Chrome Web Store integration are not supported.
|
||||||
# Install dependencies
|
|
||||||
npm install
|
|
||||||
# Run the app
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT Modified](LICENSE.md)
|
[SPL-R5](https://github.com/Sneed-Group/sneedium/blob/master/LICENSE.md)
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
"Insider's Look" builds are outdated and meant for people who are just testing core functionality by default, and aren't interested in changes! If you want changes, compile it or download the new binaries.
|
"Insider's Look" builds are outdated. If you want changes, compile it or download the new binaries.
|
||||||
|
|
||||||
## What is Privacy Redirection Technology (PRT)?
|
## What is Privacy Redirection Technology (PRT)?
|
||||||
|
|
||||||
This is a simple technology that replaces a bad URL with a more privacy friendly service, when we know that ADs aren't 100% blocked, or just to provide a better UX. TLDR: Clickjacking for good.
|
This is a simple technology that replaces a bad URL with a more privacy friendly service, when we know that ADs aren't 100% blocked, or just to provide a better UX. TLDR: Clickjacking for the greater good. *This will NEVER use affilate links or whatnot, and how it works can be seen [here.](https://sneedgit.nodemixaholic.com/Sneed-Group/sneedium/src/branch/master/libbrowz.js)*
|
||||||
|
|
187
ad-blocker.js
Normal file
187
ad-blocker.js
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
/**
|
||||||
|
* Ad Blocker Module
|
||||||
|
*
|
||||||
|
* Handles ad blocking and web request filtering for Sneedium browser:
|
||||||
|
* - Sets up ad blocking with the Ghostery adblocker
|
||||||
|
* - Configures web request rules
|
||||||
|
* - Prevents access to local resources from remote sites
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { ElectronBlocker } = require('@ghostery/adblocker-electron');
|
||||||
|
const fetch = require("cross-fetch");
|
||||||
|
|
||||||
|
// Blocklist for ad filtering
|
||||||
|
const toBlock = [
|
||||||
|
"*://*.doubleclick.*",
|
||||||
|
"*://s.innovid.com/*",
|
||||||
|
"*://partner.googleadservices.com/*",
|
||||||
|
"*://*.googlesyndication.com/*",
|
||||||
|
"*://*.google-analytics.com/*",
|
||||||
|
"*://*.creative.ak.fbcdn.net/*",
|
||||||
|
"*://*.adbrite.com/*",
|
||||||
|
"*://*.exponential.com/*",
|
||||||
|
"*://*.quantserve.com/*",
|
||||||
|
"*://*.scorecardresearch.com/*",
|
||||||
|
"*://*.zedo.com/*",
|
||||||
|
"*://*.a-ads.com/*",
|
||||||
|
"*://*.777partner.com/*",
|
||||||
|
"*://*.77tracking.com/*",
|
||||||
|
"*://*.abc-ads.com/*",
|
||||||
|
"*://*.aaxads.com/*",
|
||||||
|
"*://*.adizio.com/*",
|
||||||
|
"*://*.adjix.com/*",
|
||||||
|
"*://*.adjug.com/*",
|
||||||
|
"*://*.adjuggler.com/*",
|
||||||
|
"*://*.trafficjunky.net/*",
|
||||||
|
"*://*.trafficleader.com/*",
|
||||||
|
"*://*.trafficrouter.io/*",
|
||||||
|
"*://*.monerominer.rocks/*",
|
||||||
|
"*://*.2mdn.net/*",
|
||||||
|
"*.exe",
|
||||||
|
"*.vbs",
|
||||||
|
"*://*.googlesyndication.*",
|
||||||
|
"*pixels*",
|
||||||
|
"*telemetry*",
|
||||||
|
"*analytics*",
|
||||||
|
"*://ads.*.com*",
|
||||||
|
"*ae/us/audience*",
|
||||||
|
"*/api/v*/science*",
|
||||||
|
"*/api/v*/typing*"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Regex patterns for additional blocking
|
||||||
|
const regexPatterns = [
|
||||||
|
"r[0-100]+---sn-.*\\.googlevideo\\.com$/g",
|
||||||
|
"r[0-100]+-sn-.*\\.googlevideo\\.com$/g"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Local domain tokens for security filtering
|
||||||
|
const locals = [
|
||||||
|
'0.0.0.0', '127.0.0.1', '192.168', '.local', '.example', '.staging', 'fe80::', '::1'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize ad blocker and web request filtering
|
||||||
|
* @param {Object} session - Electron session to apply blocking to
|
||||||
|
* @returns {Object} Functions to control ad blocker
|
||||||
|
*/
|
||||||
|
function initAdBlocker(session) {
|
||||||
|
let blocker = undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
enableAdBlocking: () => enableGoodies(session),
|
||||||
|
setupWebRequestFilters: () => setupWebRequestFilters(session),
|
||||||
|
isLocal
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable ad blocking for a session
|
||||||
|
* @param {Object} s - Electron session to enable blocking for
|
||||||
|
*/
|
||||||
|
async function enableGoodies(s) {
|
||||||
|
try {
|
||||||
|
if (!s) {
|
||||||
|
console.log("No session provided to enableGoodies, using default session");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocker = await ElectronBlocker.fromLists(fetch, [
|
||||||
|
"https://github.com/uBlockOrigin/uAssets/raw/refs/heads/master/filters/ubol-filters.txt",
|
||||||
|
'https://easylist.to/easylist/easylist.txt',
|
||||||
|
'https://secure.fanboy.co.nz/fanboy-annoyance.txt',
|
||||||
|
'https://easylist.to/easylist/easyprivacy.txt',
|
||||||
|
'https://easylist-downloads.adblockplus.org/antiadblockfilters.txt',
|
||||||
|
'https://raw.githubusercontent.com/hoshsadiq/adblock-nocoin-list/master/nocoin.txt',
|
||||||
|
'https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/adblock/pro.plus.txt',
|
||||||
|
"https://github.com/yokoffing/filterlists/raw/refs/heads/main/youtube_clear_view.txt",
|
||||||
|
"https://pgl.yoyo.org/as/serverlist.php?showintro=0;hostformat=adblock",
|
||||||
|
"https://github.com/uBlockOrigin/uAssets/raw/refs/heads/master/filters/unbreak.txt",
|
||||||
|
"https://github.com/uBlockOrigin/uAssets/raw/refs/heads/master/filters/quick-fixes.txt",
|
||||||
|
"https://github.com/uBlockOrigin/uAssets/raw/refs/heads/master/filters/privacy.txt",
|
||||||
|
"https://github.com/uBlockOrigin/uAssets/raw/refs/heads/master/filters/badware.txt",
|
||||||
|
"https://github.com/uBlockOrigin/uAssets/raw/refs/heads/master/filters/filters.txt"
|
||||||
|
]);
|
||||||
|
|
||||||
|
blocker.enableBlockingInSession(s);
|
||||||
|
console.log("AD blocking enabled successfully");
|
||||||
|
return blocker;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error enabling AD blocking:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a URL is for a local resource
|
||||||
|
* @param {string} url - URL to check
|
||||||
|
* @returns {boolean} Whether URL is for a local resource
|
||||||
|
*/
|
||||||
|
function isLocal(url) {
|
||||||
|
return locals.some(local => url.includes(local));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a URL should be blocked by our custom rules
|
||||||
|
* @param {string} url - URL to check
|
||||||
|
* @returns {boolean} Whether URL should be blocked
|
||||||
|
*/
|
||||||
|
function containsAD(url) {
|
||||||
|
// Check against simple pattern list
|
||||||
|
for (let i = 0; i < toBlock.length; i++) {
|
||||||
|
let regex = toBlock[i].replace(/\*/g, "[^ ]*");
|
||||||
|
if (url.match(regex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check against regex patterns
|
||||||
|
for (let i = 0; i < regexPatterns.length; i++) {
|
||||||
|
let regex = regexPatterns[i];
|
||||||
|
if (url.match(regex)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up web request filters for ad blocking and security
|
||||||
|
* @param {Object} session - Electron session to set up filters for
|
||||||
|
*/
|
||||||
|
function setupWebRequestFilters(session) {
|
||||||
|
// Set up web request filter
|
||||||
|
session.webRequest.onBeforeRequest((details, callback) => {
|
||||||
|
// Block ads
|
||||||
|
if (containsAD(details.url)) {
|
||||||
|
return callback({cancel: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(details.url);
|
||||||
|
const hostname = url.hostname;
|
||||||
|
const isLocalDomain = isLocal(hostname);
|
||||||
|
|
||||||
|
// Check if the request is to a local domain
|
||||||
|
if (isLocalDomain) {
|
||||||
|
// Check if the request is initiated by a remote domain
|
||||||
|
const initiator = details.initiator ? new URL(details.initiator).hostname : '';
|
||||||
|
const isInitiatorLocal = isLocal(initiator);
|
||||||
|
|
||||||
|
if (initiator && !isInitiatorLocal) {
|
||||||
|
console.log(`[W] Local domain is being accessed by external source (${initiator}), don't allow!`);
|
||||||
|
callback({ cancel: true }); // Block request to local domains from remote sources
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({ cancel: false }); // Allow request
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in web request filter:", error);
|
||||||
|
callback({ cancel: false }); // Allow on error
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = initAdBlocker;
|
112
crx-extractor.js
Normal file
112
crx-extractor.js
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/**
|
||||||
|
* CRX Extractor - Utility for handling Chrome extension files
|
||||||
|
* This module provides functions to extract and install Chrome extensions from CRX files
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const AdmZip = require('adm-zip');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a CRX file to the specified directory
|
||||||
|
*
|
||||||
|
* @param {string} crxFilePath - Path to the CRX file
|
||||||
|
* @param {string} outputDir - Directory to extract the CRX file to
|
||||||
|
* @returns {Promise<Object>} Result of the extraction
|
||||||
|
*/
|
||||||
|
async function extractCrxFile(crxFilePath, outputDir) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// Read the CRX file
|
||||||
|
const crxData = fs.readFileSync(crxFilePath);
|
||||||
|
|
||||||
|
// Check CRX header (Chrome extensions start with "Cr24")
|
||||||
|
if (crxData.slice(0, 4).toString() !== 'Cr24') {
|
||||||
|
return reject(new Error('Invalid CRX file format'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRX format:
|
||||||
|
// [0-3] "Cr24" (Chrome extension signature)
|
||||||
|
// [4-7] Version number (uint32)
|
||||||
|
// [8-11] Public key length (uint32)
|
||||||
|
// [12-15] Signature length (uint32)
|
||||||
|
|
||||||
|
// Extract header information
|
||||||
|
const publicKeyLength = crxData.readUInt32LE(8);
|
||||||
|
const signatureLength = crxData.readUInt32LE(12);
|
||||||
|
|
||||||
|
// ZIP data starts after the header, public key, and signature
|
||||||
|
const zipStartOffset = 16 + publicKeyLength + signatureLength;
|
||||||
|
|
||||||
|
// Extract the ZIP portion of the CRX file
|
||||||
|
const zipData = crxData.slice(zipStartOffset);
|
||||||
|
|
||||||
|
// Write the ZIP data to a temporary file
|
||||||
|
const tempZipPath = path.join(require('os').tmpdir(), `${path.basename(crxFilePath, '.crx')}.zip`);
|
||||||
|
fs.writeFileSync(tempZipPath, zipData);
|
||||||
|
|
||||||
|
// Extract the ZIP file using AdmZip
|
||||||
|
const zip = new AdmZip(tempZipPath);
|
||||||
|
zip.extractAllTo(outputDir, true);
|
||||||
|
|
||||||
|
// Clean up the temporary ZIP file
|
||||||
|
fs.unlinkSync(tempZipPath);
|
||||||
|
|
||||||
|
// Check if manifest.json exists in the extracted files
|
||||||
|
const manifestPath = path.join(outputDir, 'manifest.json');
|
||||||
|
if (!fs.existsSync(manifestPath)) {
|
||||||
|
return reject(new Error('No manifest.json found in the extension'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the manifest.json file to get extension information
|
||||||
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
manifest,
|
||||||
|
extensionId: manifest.name ? manifest.name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase() : null
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a Chrome extension from a CRX file
|
||||||
|
*
|
||||||
|
* @param {string} crxFilePath - Path to the CRX file
|
||||||
|
* @param {string} extensionsDir - Directory where extensions are stored
|
||||||
|
* @returns {Promise<Object>} Result of the installation
|
||||||
|
*/
|
||||||
|
async function installCrxExtension(crxFilePath, extensionsDir) {
|
||||||
|
try {
|
||||||
|
const extensionId = path.basename(crxFilePath, '.crx');
|
||||||
|
const extractPath = path.join(extensionsDir, extensionId);
|
||||||
|
|
||||||
|
// Create directory if it doesn't exist
|
||||||
|
if (!fs.existsSync(extractPath)) {
|
||||||
|
fs.mkdirSync(extractPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the CRX file
|
||||||
|
const extractResult = await extractCrxFile(crxFilePath, extractPath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Extension installed successfully',
|
||||||
|
extensionId,
|
||||||
|
manifest: extractResult.manifest
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `Failed to install extension: ${error.message}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extractCrxFile,
|
||||||
|
installCrxExtension
|
||||||
|
};
|
BIN
download.png
Normal file
BIN
download.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 236 KiB |
194
extension-manager.js
Normal file
194
extension-manager.js
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
/**
|
||||||
|
* Extension Manager Module
|
||||||
|
*
|
||||||
|
* Handles Chrome extension management for Sneedium browser
|
||||||
|
* - Loading extensions from disk
|
||||||
|
* - Installing extensions from CRX files
|
||||||
|
* - Removing extensions
|
||||||
|
* - Creating extensions management UI
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||||||
|
const { installCrxExtension } = require('./crx-extractor');
|
||||||
|
|
||||||
|
// Define extension directory path
|
||||||
|
const homePath = app.getPath('home');
|
||||||
|
const extensionsDir = path.join(homePath, 'SneedExtensions');
|
||||||
|
|
||||||
|
// Ensure extensions directory exists
|
||||||
|
if (!fs.existsSync(extensionsDir)) {
|
||||||
|
fs.mkdirSync(extensionsDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize extension manager and set up IPC handlers
|
||||||
|
* @returns {Object} Extension manager functions
|
||||||
|
*/
|
||||||
|
function initExtensionManager() {
|
||||||
|
// Set up IPC handlers for extension management
|
||||||
|
setupIpcHandlers();
|
||||||
|
|
||||||
|
return {
|
||||||
|
getExtensionsDir: () => extensionsDir,
|
||||||
|
createExtensionsWindow,
|
||||||
|
loadExtensions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up IPC handlers for extension management
|
||||||
|
*/
|
||||||
|
function setupIpcHandlers() {
|
||||||
|
// Get list of installed extensions
|
||||||
|
ipcMain.handle('get-extensions-list', getExtensionsList);
|
||||||
|
|
||||||
|
// Install extension from CRX file
|
||||||
|
ipcMain.handle('install-extension', handleInstallExtension);
|
||||||
|
|
||||||
|
// Remove installed extension
|
||||||
|
ipcMain.handle('remove-extension', handleRemoveExtension);
|
||||||
|
|
||||||
|
// Open extension management window
|
||||||
|
ipcMain.on('open-extensions-page', () => createExtensionsWindow());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of installed extensions with their metadata
|
||||||
|
* @returns {Array} List of extension objects with metadata
|
||||||
|
*/
|
||||||
|
async function getExtensionsList() {
|
||||||
|
try {
|
||||||
|
const extensionFolders = fs.readdirSync(extensionsDir, { withFileTypes: true })
|
||||||
|
.filter(dirent => dirent.isDirectory())
|
||||||
|
.map(dirent => {
|
||||||
|
const extPath = path.join(extensionsDir, dirent.name);
|
||||||
|
let manifest = {};
|
||||||
|
try {
|
||||||
|
const manifestPath = path.join(extPath, 'manifest.json');
|
||||||
|
if (fs.existsSync(manifestPath)) {
|
||||||
|
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error reading manifest for extension ${dirent.name}:`, e);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: dirent.name,
|
||||||
|
name: manifest.name || dirent.name,
|
||||||
|
version: manifest.version || 'unknown',
|
||||||
|
description: manifest.description || '',
|
||||||
|
path: extPath
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return extensionFolders;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting extensions list:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle extension installation request
|
||||||
|
* @param {Object} event - IPC event
|
||||||
|
* @param {string} crxPath - Path to CRX file or null to prompt for file
|
||||||
|
* @returns {Object} Result of installation
|
||||||
|
*/
|
||||||
|
async function handleInstallExtension(event, crxPath) {
|
||||||
|
try {
|
||||||
|
// If no path provided, open dialog to select CRX file
|
||||||
|
if (!crxPath) {
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [{ name: 'Chrome Extension', extensions: ['crx'] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled || result.filePaths.length === 0) {
|
||||||
|
return { success: false, message: 'No file selected' };
|
||||||
|
}
|
||||||
|
|
||||||
|
crxPath = result.filePaths[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the CRX extractor to install the extension
|
||||||
|
const installResult = await installCrxExtension(crxPath, extensionsDir);
|
||||||
|
|
||||||
|
return installResult;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error installing extension:', error);
|
||||||
|
return { success: false, message: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle extension removal request
|
||||||
|
* @param {Object} event - IPC event
|
||||||
|
* @param {string} extensionId - ID of extension to remove
|
||||||
|
* @returns {Object} Result of removal
|
||||||
|
*/
|
||||||
|
async function handleRemoveExtension(event, extensionId) {
|
||||||
|
try {
|
||||||
|
const extensionPath = path.join(extensionsDir, extensionId);
|
||||||
|
if (fs.existsSync(extensionPath)) {
|
||||||
|
fs.rmSync(extensionPath, { recursive: true, force: true });
|
||||||
|
return { success: true };
|
||||||
|
} else {
|
||||||
|
return { success: false, message: 'Extension not found' };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error removing extension:', error);
|
||||||
|
return { success: false, message: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create extension management window
|
||||||
|
*/
|
||||||
|
function createExtensionsWindow() {
|
||||||
|
const extensionsWindow = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
title: 'Extension Manager',
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'extensions-preload.js'),
|
||||||
|
nodeIntegration: false,
|
||||||
|
contextIsolation: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
extensionsWindow.loadFile('extensions.html');
|
||||||
|
extensionsWindow.setMenuBarVisibility(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all extensions from the extensions directory
|
||||||
|
* @param {Object} extensionsInstance - ElectronChromeExtensions instance
|
||||||
|
*/
|
||||||
|
function loadExtensions(extensionsInstance) {
|
||||||
|
if (!extensionsInstance) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get list of extension directories
|
||||||
|
const extensionFolders = fs.readdirSync(extensionsDir, { withFileTypes: true })
|
||||||
|
.filter(dirent => dirent.isDirectory())
|
||||||
|
.map(dirent => path.join(extensionsDir, dirent.name));
|
||||||
|
|
||||||
|
// Load each extension
|
||||||
|
for (const extensionPath of extensionFolders) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(path.join(extensionPath, 'manifest.json'))) {
|
||||||
|
const loadResult = extensionsInstance.loadExtension(extensionPath);
|
||||||
|
console.log(`Loaded extension from ${extensionPath}`, loadResult);
|
||||||
|
} else {
|
||||||
|
console.warn(`Missing manifest.json in extension directory: ${extensionPath}`);
|
||||||
|
}
|
||||||
|
} catch (extError) {
|
||||||
|
console.error(`Failed to load extension at ${extensionPath}:`, extError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading extensions:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = initExtensionManager;
|
17
extensions-preload.js
Normal file
17
extensions-preload.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Preload script for the extensions management page
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
// Expose extension management functions to the renderer process
|
||||||
|
contextBridge.exposeInMainWorld('extensionsAPI', {
|
||||||
|
// Get list of installed extensions
|
||||||
|
getExtensions: () => ipcRenderer.invoke('get-extensions-list'),
|
||||||
|
|
||||||
|
// Install a new extension (from CRX file)
|
||||||
|
installExtension: (crxPath) => ipcRenderer.invoke('install-extension', crxPath),
|
||||||
|
|
||||||
|
// Remove an extension
|
||||||
|
removeExtension: (extensionId) => ipcRenderer.invoke('remove-extension', extensionId),
|
||||||
|
|
||||||
|
// Open file dialog to select CRX file
|
||||||
|
openFileDialog: () => ipcRenderer.invoke('install-extension')
|
||||||
|
});
|
164
extensions.html
Normal file
164
extensions.html
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Extension Manager</title>
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<style>
|
||||||
|
.extension-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-card {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-card h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extension-card p {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install-btn {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-btn {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-extensions {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>Extension Manager</h1>
|
||||||
|
<button id="install-extension" class="button install-btn">Install Extension</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="extension-container" id="extensions-list">
|
||||||
|
<div class="no-extensions">No extensions installed</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Get extensions list
|
||||||
|
async function loadExtensions() {
|
||||||
|
try {
|
||||||
|
const extensions = await window.extensionsAPI.getExtensions();
|
||||||
|
const extensionsList = document.getElementById('extensions-list');
|
||||||
|
|
||||||
|
// Clear current list
|
||||||
|
extensionsList.innerHTML = '';
|
||||||
|
|
||||||
|
if (extensions.length === 0) {
|
||||||
|
extensionsList.innerHTML = '<div class="no-extensions">No extensions installed</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate list with extension cards
|
||||||
|
extensions.forEach(extension => {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'extension-card';
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="extension-info">
|
||||||
|
<h3>${extension.name || 'Unknown Extension'}</h3>
|
||||||
|
<p class="version">Version: ${extension.version || 'unknown'}</p>
|
||||||
|
<p>${extension.description || 'No description available'}</p>
|
||||||
|
</div>
|
||||||
|
<div class="extension-actions">
|
||||||
|
<button class="button remove-btn" data-id="${extension.id}">Remove</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
extensionsList.appendChild(card);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add event listeners to remove buttons
|
||||||
|
document.querySelectorAll('.remove-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', async (e) => {
|
||||||
|
const extensionId = e.target.getAttribute('data-id');
|
||||||
|
const result = await window.extensionsAPI.removeExtension(extensionId);
|
||||||
|
if (result.success) {
|
||||||
|
loadExtensions(); // Refresh list
|
||||||
|
} else {
|
||||||
|
alert('Failed to remove extension: ' + (result.message || 'Unknown error'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading extensions:', error);
|
||||||
|
alert('Failed to load extensions: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install button click handler
|
||||||
|
document.getElementById('install-extension').addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
const result = await window.extensionsAPI.openFileDialog();
|
||||||
|
if (result.success) {
|
||||||
|
loadExtensions(); // Refresh list
|
||||||
|
} else if (result.message) {
|
||||||
|
alert(result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error installing extension:', error);
|
||||||
|
alert('Failed to install extension: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load extensions when page loads
|
||||||
|
document.addEventListener('DOMContentLoaded', loadExtensions);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -4,7 +4,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'">
|
||||||
<title>F-Stopium</title>
|
<title>Sneedium</title>
|
||||||
<link href="./styles.css" rel="stylesheet">
|
<link href="./styles.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -12,15 +12,19 @@
|
||||||
<button id="nwBtn">📄</button>
|
<button id="nwBtn">📄</button>
|
||||||
<input id="txtUrl" placeholder="Put the website here" name="url" type="text" onkeypress="clickPress(event)" />
|
<input id="txtUrl" placeholder="Put the website here" name="url" type="text" onkeypress="clickPress(event)" />
|
||||||
<button onclick="go(); return false;" id="goBtn">✅</button>
|
<button onclick="go(); return false;" id="goBtn">✅</button>
|
||||||
|
<button onclick="refresh(); return false;" id="refreshBtn">🔄</button>
|
||||||
<button onclick="stop(); return false;" id="stopBtn">🛑</button>
|
<button onclick="stop(); return false;" id="stopBtn">🛑</button>
|
||||||
<button onclick="back(); return false;" id="backBtn">⬅️</button>
|
<button onclick="back(); return false;" id="backBtn">⬅️</button>
|
||||||
<button onclick="forward(); return false;" id="forwardBtn">➡️</button>
|
<button onclick="forward(); return false;" id="forwardBtn">➡️</button>
|
||||||
|
<button id="camBtn">🎦</button>
|
||||||
|
<button id="extBtn" title="Manage Extensions">🧩</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="whProtection">
|
||||||
<tab-group new-tab-button="true" sortable="true">
|
<tab-group new-tab-button="true" sortable="true">
|
||||||
<webview id="foo" autosize plugins disablewebsecurity></webview>
|
<webview id="foo" autosize plugins disablewebsecurity></webview>
|
||||||
</tab-group>
|
</tab-group>
|
||||||
|
</div>
|
||||||
<script src="node_modules/electron-tabs/dist/electron-tabs.js"></script>
|
<script src="node_modules/electron-tabs/dist/electron-tabs.js"></script>
|
||||||
<script type="text/javascript" src="./userscript/userscripts.js"></script>
|
|
||||||
<script type="text/javascript" src="./config.js"></script>
|
<script type="text/javascript" src="./config.js"></script>
|
||||||
<script type="text/javascript" src="./libbrowz.js"></script>
|
<script type="text/javascript" src="./libbrowz.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
|
<!-- This is the Sneedium new tab page. -->
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
|
||||||
<title>F-Stopium works!</title>
|
<title>Sneedium works!</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<img src="logo.png" align="right" width="20%"/>
|
<img src="logo.png" align="right" width="20%"/>
|
||||||
<h1>Welcome to F-Stopium!</h1>
|
<h1>Welcome to Sneedium!</h1>
|
||||||
<p>Put a list of URLs to userscripts in ./userscript/userscripts.js. It uses a standard JS array format and uses URLs. Config is located at: ./config.js.</p>
|
<p>Welcome to the Sneedium Browser! You can easily compile for for macOS, Windows, and Linux as of the time of writing this. We hope you enjoy this browser as much as we did making it.</br></br>From all of us at Sneed Group.
|
||||||
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
84
libbrowz.js
84
libbrowz.js
|
@ -1,22 +1,69 @@
|
||||||
|
const userAgents = [
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/128.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/120.0.0.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/98.0.0.0",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0"
|
||||||
|
];
|
||||||
|
|
||||||
|
function getRandomUserAgent() {
|
||||||
|
const randomIndex = Math.floor(Math.random() * userAgents.length);
|
||||||
|
return userAgents[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentUA = "" // We haven't loaded any websites yet. It should be blank.
|
||||||
|
|
||||||
let tabGroup = document.querySelector("tab-group");
|
let tabGroup = document.querySelector("tab-group");
|
||||||
|
function normalizeUrl(url) {
|
||||||
|
// Define regex patterns for matching URL schemes and local addresses
|
||||||
|
const httpPattern = /^http:\/\//i;
|
||||||
|
const httpsPattern = /^https:\/\//i;
|
||||||
|
const filePattern = /^file:\/\//i;
|
||||||
|
const indexPattern = /^index\.html/i;
|
||||||
|
const localPattern = /^(192\.168|127\.0|localhost)/i;
|
||||||
|
|
||||||
|
// Check if the URL already has a valid scheme
|
||||||
|
if (httpPattern.test(url) || httpsPattern.test(url) || filePattern.test(url) || indexPattern.test(url)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if the URL starts with a local address or needs HTTPS
|
||||||
|
if (localPattern.test(url)) {
|
||||||
|
return `http://${url}`;
|
||||||
|
} else {
|
||||||
|
return `https://${url}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = undefined
|
||||||
|
let browserFrame = undefined
|
||||||
function go() {
|
function go() {
|
||||||
let browserFrame = tabGroup.getActiveTab().webview
|
currentUA = getRandomUserAgent()
|
||||||
|
browserFrame = tabGroup.getActiveTab().webview
|
||||||
let browser = tabGroup.getActiveTab()
|
let browser = tabGroup.getActiveTab()
|
||||||
let url = document.getElementById("txtUrl").value
|
url = normalizeUrl(document.getElementById("txtUrl").value)
|
||||||
if (url.includes("youtube.com") || url.includes("youtu.be")) {
|
if (url.includes("youtube.com") || url.includes("youtu.be")) {
|
||||||
url = url.replaceAll("youtube.com", "yewtu.be")
|
url = url.replaceAll("youtube.com", "samtube.nodemixaholic.com")
|
||||||
url = url.replaceAll("youtu.be", "yewtu.be")
|
url = url.replaceAll("youtu.be", "samtube.nodemixaholic.com")
|
||||||
} else if (url.includes("google.com/?q")) {
|
} else if (url.includes("google.com/search?q") || url.includes("google.com/?q")) {
|
||||||
url = url.replaceAll("google.com/?q", "startpage.com/?q")
|
// Define the URL object
|
||||||
} else if (url.includes("google.com") && !url.includes("maps") && !url.includes("news") && !url.includes("webstore") && !url.includes("mail")) {
|
const serachUrlObj = new URL(url);
|
||||||
url = url.replaceAll("google.com", "startpage.com")
|
//Get query from old url
|
||||||
|
const query = serachUrlObj.searchParams.get('q');
|
||||||
|
// Define the pattern for URL replacement
|
||||||
|
const searchPattern = "https://search.sparksammy.com/search.php?q=!!!QUERY!!!&p=0&t=0";
|
||||||
|
url = searchPattern.replaceAll("!!!QUERY!!!", query)
|
||||||
} else if (url.includes("https://news.google.com")) {
|
} else if (url.includes("https://news.google.com")) {
|
||||||
url = url.replaceAll("https://news.google.com", "http://68k.news")
|
url = url.replaceAll("https://news.google.com", "http://68k.news")
|
||||||
} else if (url.includes("news.google.com")) {
|
} else if (url.includes("google.com") && !url.includes("maps") && !url.includes("news") && !url.includes("webstore") && !url.includes("drive") && !url.includes("docs") && !url.includes("sheets") && !url.includes("slides") && !url.includes("mail") && !url.includes("webstore") && !url.includes("in") && !url.includes("on") && !url.includes("jp")) {
|
||||||
url = url.replaceAll("news.google.com", "68k.news")
|
url = url.replaceAll("google.com", "search.sparksammy.com")
|
||||||
}
|
}
|
||||||
document.getElementById("txtUrl").value = ""
|
document.getElementById("txtUrl").value = ""
|
||||||
browserFrame.loadURL(url);
|
browserFrame.loadURL(url,
|
||||||
|
{userAgent: currentUA});
|
||||||
browserFrame.addEventListener('dom-ready', () => {
|
browserFrame.addEventListener('dom-ready', () => {
|
||||||
browserFrame.insertCSS(`
|
browserFrame.insertCSS(`
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
|
@ -30,11 +77,6 @@ function go() {
|
||||||
tabGroup.getActiveTab().setTitle(title)
|
tabGroup.getActiveTab().setTitle(title)
|
||||||
console.log(title)
|
console.log(title)
|
||||||
})
|
})
|
||||||
for (let i = 0; i < userscripts.length; i++) {
|
|
||||||
fetch(userscripts[i]).then( r => r.text() ).then( t => userscripts.executeJavaScript(t)).catch(() => {
|
|
||||||
console.log("Error loading userscripts! (Did you provide any?)")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
|
@ -52,6 +94,15 @@ function forward() {
|
||||||
browserFrame.goForward()
|
browserFrame.goForward()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
if (typeof url != undefined) {
|
||||||
|
browserFrame.loadURL(browserFrame.getURL(),
|
||||||
|
{userAgent: currentUA});
|
||||||
|
} else {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tabGroup.setDefaultTab({
|
tabGroup.setDefaultTab({
|
||||||
title: CONF.homepageTitle,
|
title: CONF.homepageTitle,
|
||||||
src: CONF.homepage,
|
src: CONF.homepage,
|
||||||
|
@ -64,4 +115,3 @@ function clickPress(keyEvent) {
|
||||||
go()
|
go()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
logo.png
BIN
logo.png
Binary file not shown.
Before ![]() (image error) Size: 29 KiB After ![]() (image error) Size: 32 KiB ![]() ![]() |
181
main.js
181
main.js
|
@ -1,135 +1,74 @@
|
||||||
// Modules to control application life and create native browser window
|
/**
|
||||||
const {app, BrowserWindow, session, ipcMain} = require('electron')
|
* Sneedium Browser - Main Process
|
||||||
const path = require('path')
|
*
|
||||||
const fetch = require("cross-fetch")
|
* This is the main entry point for the Electron application.
|
||||||
const { ElectronChromeExtensions } = require('electron-chrome-extensions')
|
* It initializes all necessary modules and manages application lifecycle.
|
||||||
|
*/
|
||||||
|
|
||||||
ipcMain.on('windowmaker', (event, arg) => {
|
const { app, BrowserWindow, ipcMain, session } = require('electron');
|
||||||
createWindow();
|
const http = require('http');
|
||||||
})
|
const { createProxy } = require('proxy');
|
||||||
|
|
||||||
function createWindow () {
|
// Import our modular components
|
||||||
// Create the browser window.
|
const initExtensionManager = require('./extension-manager');
|
||||||
|
const initAdBlocker = require('./ad-blocker');
|
||||||
|
const initWindowManager = require('./window-manager');
|
||||||
|
|
||||||
const extensions = new ElectronChromeExtensions({
|
// Create HTTP proxy server
|
||||||
session: session.defaultSession
|
const proxy = createProxy(http.createServer());
|
||||||
})
|
proxy.listen(3129);
|
||||||
|
|
||||||
const mainWindow = new BrowserWindow({
|
// Initialize our modules
|
||||||
width: 1000,
|
const extensionManager = initExtensionManager();
|
||||||
height: 600,
|
const adBlocker = initAdBlocker(session.defaultSession);
|
||||||
webPreferences: {
|
const windowManager = initWindowManager(adBlocker, extensionManager);
|
||||||
preload: path.join(__dirname, 'preload.js'),
|
|
||||||
webviewTag: true,
|
|
||||||
devTools: false,
|
|
||||||
nodeIntegration: true,
|
|
||||||
sandbox: true,
|
|
||||||
contextIsolation: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
mainWindow.removeMenu()
|
// Handle IPC messages from renderer process
|
||||||
mainWindow.setMinimumSize(1000, 300)
|
setupIpcHandlers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up IPC handlers for renderer process
|
||||||
|
*/
|
||||||
|
function setupIpcHandlers() {
|
||||||
|
// Create a new window when requested
|
||||||
|
ipcMain.on('windowmaker', () => {
|
||||||
|
windowManager.createWindow();
|
||||||
|
});
|
||||||
|
|
||||||
const toBlock = [
|
// Request media access permissions
|
||||||
"*://*.doubleclick.*",
|
ipcMain.on('allowCam', () => {
|
||||||
"*://s.innovid.com/*",
|
windowManager.setMediaAccess();
|
||||||
"*://partner.googleadservices.com/*",
|
});
|
||||||
"*://*.googlesyndication.com/*",
|
|
||||||
"*://*.google-analytics.com/*",
|
|
||||||
"*://creative.ak.fbcdn.net/*",
|
|
||||||
"*://*.adbrite.com/*",
|
|
||||||
"*://*.exponential.com/*",
|
|
||||||
"*://*.quantserve.com/*",
|
|
||||||
"*://*.scorecardresearch.com/*",
|
|
||||||
"*://*.zedo.com/*",
|
|
||||||
"*://*.a-ads.com/*",
|
|
||||||
"*://*.777partner.com/*",
|
|
||||||
"*://*.77tracking.com/*",
|
|
||||||
"*://*.abc-ads.com/*",
|
|
||||||
"*://*.aaxads.com/*",
|
|
||||||
"*://*.adizio.com/*",
|
|
||||||
"*://*.adjix.com/*",
|
|
||||||
"*://*.adjug.com/*",
|
|
||||||
"*://*.adjuggler.com/*",
|
|
||||||
"*://*.trafficjunky.net/*",
|
|
||||||
"*://*.trafficleader.com/*",
|
|
||||||
"*://*.trafficrouter.io/*",
|
|
||||||
"*://*.monerominer.rocks/*",
|
|
||||||
"*://*.2mdn.net/*",
|
|
||||||
"*.exe",
|
|
||||||
"*.vbs",
|
|
||||||
"*://*.googlesyndication.*",
|
|
||||||
"*pixels*",
|
|
||||||
"*telemetry*",
|
|
||||||
"*analytics*",
|
|
||||||
"*://ads.*.com*",
|
|
||||||
"*ae/us/audience*",
|
|
||||||
"*/api/v*/science*",
|
|
||||||
"*/api/v*/typing*"
|
|
||||||
]
|
|
||||||
|
|
||||||
const regexPatterns = [
|
|
||||||
"r[0-100]+---sn-.*\.googlevideo\.com$/g",
|
|
||||||
"r[0-100]+-sn-.*\.googlevideo\.com$/g"
|
|
||||||
]
|
|
||||||
|
|
||||||
function containsAD(url) {
|
|
||||||
var i;
|
|
||||||
for (i = 0; i < toBlock.length; i++) {
|
|
||||||
let regex = toBlock[i].replace(/\*/g, "[^ ]*");
|
|
||||||
if (url.match(regex)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < regexPatterns.length; i++) {
|
// This method will be called when Electron has finished initialization
|
||||||
let regex = regexPatterns[i]
|
// and is ready to create browser windows.
|
||||||
if (url.match(regex)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
|
|
||||||
if (containsAD(details.url)) {
|
|
||||||
return callback({cancel: true})
|
|
||||||
}
|
|
||||||
return callback({})
|
|
||||||
})
|
|
||||||
|
|
||||||
extensions.addTab(mainWindow.webContents, mainWindow)
|
|
||||||
|
|
||||||
// and load the index.html of the app.
|
|
||||||
mainWindow.loadFile('index.html')
|
|
||||||
|
|
||||||
// Open the DevTools.
|
|
||||||
mainWindow.webContents.openDevTools()
|
|
||||||
|
|
||||||
return mainWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
|
||||||
// initialization and is ready to create browser windows.
|
|
||||||
// Some APIs can only be used after this event occurs.
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
let x = createWindow()
|
// Create main window
|
||||||
|
const mainWindow = windowManager.createWindow();
|
||||||
|
|
||||||
|
// Enable ad blocking
|
||||||
|
adBlocker.enableAdBlocking(session.defaultSession).then(() => {
|
||||||
|
console.log("Ad blocking enabled");
|
||||||
|
}).catch(error => {
|
||||||
|
console.error("Error enabling ad blocking:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// On macOS it's common to re-create a window when the
|
||||||
|
// dock icon is clicked and there are no other windows open
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
// On macOS it's common to re-create a window in the app when the
|
if (BrowserWindow.getAllWindows().length === 0) windowManager.createWindow();
|
||||||
// dock icon is clicked and there are no other windows open.
|
});
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
});
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
// Quit when all windows are closed, except on macOS
|
||||||
// for applications and their menu bar to stay active until the user quits
|
|
||||||
// explicitly with Cmd + Q.
|
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function () {
|
||||||
if (process.platform !== 'darwin') app.quit()
|
if (process.platform !== 'darwin') {
|
||||||
})
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
// Set up DNS configuration when app is ready
|
||||||
// code. You can also put them in separate files and require them here.
|
app.on('ready', () => {
|
||||||
|
windowManager.setupDnsConfig();
|
||||||
|
});
|
7704
package-lock.json
generated
Normal file
7704
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
34
package.json
34
package.json
|
@ -1,23 +1,27 @@
|
||||||
{
|
{
|
||||||
"name": "fstopium",
|
"name": "sneedium",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A Electron browser",
|
"description": "A Electron browser with Chrome extension support",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron-forge start",
|
"start": "electron-forge start",
|
||||||
"package": "electron-forge package",
|
"package": "electron-forge package",
|
||||||
"make": "npx electron-packager --platform linux,win32 --arch x64,arm64 ."
|
"make": "npx electron-packager --platform linux,win32 --arch x64,arm64 .",
|
||||||
|
"make-mac": "npx electron-packager --platform darwin --arch x64,arm64 .",
|
||||||
|
"dev": "cross-env NODE_ENV=development electron ."
|
||||||
},
|
},
|
||||||
"repository": "https://github.com/electron/electron-quick-start",
|
"repository": "https://github.com/Sneed-Group/sneedium",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Electron",
|
"Electron",
|
||||||
"quick",
|
"quick",
|
||||||
"start",
|
"start",
|
||||||
"tutorial",
|
"tutorial",
|
||||||
"demo"
|
"demo",
|
||||||
|
"browser",
|
||||||
|
"extension"
|
||||||
],
|
],
|
||||||
"author": "FStop Tech",
|
"author": "Sneed Group",
|
||||||
"license": "MIT (Modified)",
|
"license": "SPL-R5",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-forge/cli": "^6.0.5",
|
"@electron-forge/cli": "^6.0.5",
|
||||||
"@electron-forge/maker-deb": "^6.0.5",
|
"@electron-forge/maker-deb": "^6.0.5",
|
||||||
|
@ -25,14 +29,20 @@
|
||||||
"@electron-forge/maker-rpm": "^6.0.5",
|
"@electron-forge/maker-rpm": "^6.0.5",
|
||||||
"@electron-forge/maker-squirrel": "^6.0.5",
|
"@electron-forge/maker-squirrel": "^6.0.5",
|
||||||
"@electron-forge/maker-zip": "^6.0.5",
|
"@electron-forge/maker-zip": "^6.0.5",
|
||||||
"electron": "^25.4.0",
|
"cross-env": "^7.0.3",
|
||||||
|
"electron": "^31.0.1",
|
||||||
"electron-forge-maker-appimage": "^24.6.3",
|
"electron-forge-maker-appimage": "^24.6.3",
|
||||||
"electron-packager": "^17.1.1"
|
"electron-packager": "^17.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ghostery/adblocker": "^2.1.1",
|
||||||
|
"@ghostery/adblocker-electron": "^2.1.1",
|
||||||
|
"adm-zip": "^0.5.16",
|
||||||
"cross-fetch": "^3.1.5",
|
"cross-fetch": "^3.1.5",
|
||||||
"electron-chrome-extensions": "^3.10.1",
|
"electron-chrome-context-menu": "^1.1.0",
|
||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-chrome-extensions": "^3.10.0",
|
||||||
"electron-tabs": "^1.0.1"
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
|
"electron-tabs": "^1.0.1",
|
||||||
|
"proxy": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
42
preload.js
42
preload.js
|
@ -5,6 +5,19 @@
|
||||||
*
|
*
|
||||||
* https://www.electronjs.org/docs/latest/tutorial/sandbox
|
* https://www.electronjs.org/docs/latest/tutorial/sandbox
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electron', {
|
||||||
|
enforceDomainRestrictions: (url) => ipcRenderer.sendSync('check-domain', url),
|
||||||
|
// Add extension management functionality
|
||||||
|
extensions: {
|
||||||
|
openExtensionsPage: () => ipcRenderer.send('open-extensions-page'),
|
||||||
|
getExtensionsList: () => ipcRenderer.invoke('get-extensions-list')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
const replaceText = (selector, text) => {
|
const replaceText = (selector, text) => {
|
||||||
const element = document.getElementById(selector)
|
const element = document.getElementById(selector)
|
||||||
|
@ -12,15 +25,36 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const type of ['chrome', 'node', 'electron']) {
|
for (const type of ['chrome', 'node', 'electron']) {
|
||||||
replaceText(`${type}-version`, process.versions[type])
|
replaceText(`${type}-version`, 'sneedium-version')
|
||||||
}
|
}
|
||||||
const { ipcRenderer } = require('electron')
|
|
||||||
ipcRenderer.on('windowmaker', (event, arg) => {
|
ipcRenderer.on('windowmaker', (event, arg) => {
|
||||||
console.log(arg) // prints "pong"
|
console.log(arg)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('allowCam', (event, arg) => {
|
||||||
|
console.log(arg)
|
||||||
})
|
})
|
||||||
//button and its event listener
|
//button and its event listener
|
||||||
const makeWindowButton = document.getElementById('nwBtn');
|
const makeWindowButton = document.getElementById('nwBtn');
|
||||||
|
if (makeWindowButton) {
|
||||||
makeWindowButton.addEventListener('click', () => {
|
makeWindowButton.addEventListener('click', () => {
|
||||||
ipcRenderer.send('windowmaker', 'ping')
|
ipcRenderer.send('windowmaker', 'ping')
|
||||||
})
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const camButton = document.getElementById('camBtn');
|
||||||
|
if (camButton) {
|
||||||
|
camButton.addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('allowCam', 'ping')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extension management button event listener
|
||||||
|
const extensionsButton = document.getElementById('extBtn');
|
||||||
|
if (extensionsButton) {
|
||||||
|
extensionsButton.addEventListener('click', () => {
|
||||||
|
ipcRenderer.send('open-extensions-page');
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,3 +5,5 @@
|
||||||
* `contextIsolation` is turned on. Use the contextBridge API in `preload.js`
|
* `contextIsolation` is turned on. Use the contextBridge API in `preload.js`
|
||||||
* to expose Node.js functionality from the main process.
|
* to expose Node.js functionality from the main process.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
require('electron-chrome-extension/preload');
|
1
sneedium-maker-mac.sh
Normal file
1
sneedium-maker-mac.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
npm run make-mac
|
10
sneedium-maker.sh
Executable file
10
sneedium-maker.sh
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
y=$(date +%Y)
|
||||||
|
read -p "Sneedium # of year $y (1,2,3,4...):" v
|
||||||
|
npm run make
|
||||||
|
mkdir "Sneedium $y.$v"
|
||||||
|
mv sneedium-linux-* "Sneedium $y.$v"
|
||||||
|
mv sneedium-win32-* "Sneedium $y.$v"
|
||||||
|
tar -czvf "Sneedium $y.$v.tar.gz" "Sneedium $y.$v"
|
||||||
|
rm -rf "Sneedium $y.$v"
|
||||||
|
mkdir ~/sneedium-bins
|
||||||
|
mv "Sneedium $y.$v.tar.gz" ~/sneedium-bins
|
51
styles.css
51
styles.css
|
@ -33,6 +33,17 @@ body {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#camBtn {
|
||||||
|
background-color: #a6b70d; /* Gold */
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 1% 1%;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
#goBtn {
|
#goBtn {
|
||||||
background-color: #4CAF50; /* Green */
|
background-color: #4CAF50; /* Green */
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -78,6 +89,17 @@ body {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#refreshBtn {
|
||||||
|
background-color: #507fac; /* Aqua */
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 1% 1%;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
#txtUrl {
|
#txtUrl {
|
||||||
border: none;
|
border: none;
|
||||||
padding: 1% 1%;
|
padding: 1% 1%;
|
||||||
|
@ -89,17 +111,30 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
|
height: 98%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
justify-content: center; /* Center horizontally */
|
||||||
|
align-items: center; /* Center vertically */
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#whProtection {
|
||||||
|
width: 100%; /* % of viewport width */
|
||||||
|
height: 100%; /* % of viewport height */
|
||||||
|
border: 2vw solid rgb(70, 70, 70); /* Border color and thickness */
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
webview {
|
webview {
|
||||||
position: absolute;
|
width: 100%;
|
||||||
top: 0;
|
height: 100%;
|
||||||
left: 0;
|
border: none;
|
||||||
max-height: 90% !important;
|
box-sizing: border-box; /* Ensures any padding or border inside the webview is included in the total size */
|
||||||
max-width: 100%;
|
display: block; /* Ensure it behaves as a block element */
|
||||||
width: 1rem;
|
|
||||||
height: 90% !important;
|
|
||||||
display: inline-flex !important;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
var userscripts = [""]
|
|
150
window-manager.js
Normal file
150
window-manager.js
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/**
|
||||||
|
* Window Manager Module
|
||||||
|
*
|
||||||
|
* Handles browser window creation and management for Sneedium browser
|
||||||
|
* - Creates main browser windows
|
||||||
|
* - Sets up context menu
|
||||||
|
* - Configures window properties
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { app, BrowserWindow, session, systemPreferences } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const buildChromeContextMenu = require('electron-chrome-context-menu').default;
|
||||||
|
const { ElectronChromeExtensions } = require('electron-chrome-extensions');
|
||||||
|
|
||||||
|
// Track permissions state
|
||||||
|
let mic = false;
|
||||||
|
let cam = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize window manager
|
||||||
|
* @param {Object} adBlocker - Ad blocker instance
|
||||||
|
* @param {Object} extensionManager - Extension manager instance
|
||||||
|
* @returns {Object} Window manager functions
|
||||||
|
*/
|
||||||
|
function initWindowManager(adBlocker, extensionManager) {
|
||||||
|
// Set up context menu handler
|
||||||
|
setupContextMenu();
|
||||||
|
|
||||||
|
let extensionsInstance = null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
createWindow: () => createWindow(extensionsInstance, adBlocker, extensionManager),
|
||||||
|
setMediaAccess,
|
||||||
|
setupDnsConfig: () => setupDnsConfig()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up context menu for all web contents
|
||||||
|
*/
|
||||||
|
function setupContextMenu() {
|
||||||
|
app.on('web-contents-created', (event, webContents) => {
|
||||||
|
webContents.on('context-menu', (e, params) => {
|
||||||
|
const menu = buildChromeContextMenu({
|
||||||
|
params,
|
||||||
|
webContents,
|
||||||
|
openLink: (url, disposition) => {
|
||||||
|
webContents.loadURL(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.popup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create main browser window
|
||||||
|
* @param {Object} extensionsInstance - Chrome extensions instance
|
||||||
|
* @param {Object} adBlocker - Ad blocker instance
|
||||||
|
* @param {Object} extensionManager - Extension manager instance
|
||||||
|
* @returns {Object} Created browser window
|
||||||
|
*/
|
||||||
|
function createWindow(extensionsInstance, adBlocker, extensionManager) {
|
||||||
|
try {
|
||||||
|
// Initialize extensions once if not already initialized
|
||||||
|
if (!extensionsInstance) {
|
||||||
|
extensionsInstance = new ElectronChromeExtensions({
|
||||||
|
// Specify extension session
|
||||||
|
session: session.defaultSession,
|
||||||
|
// Set to true to enable background pages for extensions
|
||||||
|
createBackgroundPages: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error initializing extensions:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainWindow = new BrowserWindow({
|
||||||
|
width: 1220,
|
||||||
|
height: 600,
|
||||||
|
minWidth: 1220,
|
||||||
|
minHeight: 600,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
webviewTag: true,
|
||||||
|
// Enable devTools for development
|
||||||
|
devTools: process.env.NODE_ENV === 'development',
|
||||||
|
nodeIntegration: false,
|
||||||
|
sandbox: true,
|
||||||
|
contextIsolation: true,
|
||||||
|
// Enable Chrome extension support
|
||||||
|
additionalArguments: ['--enable-features=ExtensionsToolbarMenu'],
|
||||||
|
// Allow access to chrome:// URLs for extensions
|
||||||
|
allowRunningInsecureContent: false,
|
||||||
|
webSecurity: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.setMinimumSize(1000, 300);
|
||||||
|
|
||||||
|
// Load installed extensions
|
||||||
|
if (extensionManager && extensionsInstance) {
|
||||||
|
extensionManager.loadExtensions(extensionsInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register window with extensions system
|
||||||
|
if (extensionsInstance) {
|
||||||
|
extensionsInstance.addTab(mainWindow.webContents, mainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up proxy for the session
|
||||||
|
session.defaultSession.setProxy({
|
||||||
|
proxyRules: 'http=localhost:3129;https=localhost:3129',
|
||||||
|
proxyBypassRules: '<local>'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup web request filters
|
||||||
|
if (adBlocker) {
|
||||||
|
adBlocker.setupWebRequestFilters(session.defaultSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the main HTML file
|
||||||
|
mainWindow.loadFile('index.html');
|
||||||
|
|
||||||
|
return mainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set media access permissions
|
||||||
|
*/
|
||||||
|
function setMediaAccess() {
|
||||||
|
mic = systemPreferences.askForMediaAccess('microphone');
|
||||||
|
cam = systemPreferences.askForMediaAccess('camera');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up secure DNS configuration
|
||||||
|
*/
|
||||||
|
function setupDnsConfig() {
|
||||||
|
app.configureHostResolver({
|
||||||
|
mode: 'secure',
|
||||||
|
dohServers: [
|
||||||
|
'https://dns9.quad9.net/dns-query',
|
||||||
|
'https://cloudflare-dns.com/dns-query'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = initWindowManager;
|
Loading…
Add table
Reference in a new issue