Add Chrome extension management functionality and update .gitignore
This commit is contained in:
parent
f11841c1a2
commit
71b407c211
12 changed files with 588 additions and 47 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
9
.cursor/rules/basic-ruleset.mdc
Normal file
9
.cursor/rules/basic-ruleset.mdc
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
description:
|
||||
globs:
|
||||
---
|
||||
|
||||
# Your rule content
|
||||
|
||||
- You can @ files here
|
||||
- You can use markdown but dont have to
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -2,4 +2,11 @@ node_modules
|
|||
seedium-linux-x64
|
||||
sneedium-win32-x64
|
||||
seedium-linux-arm64
|
||||
sneedium-win32-arm64
|
||||
sneedium-win32-arm64
|
||||
sneedium-darwin-x64
|
||||
sneedium-darwin-arm64
|
||||
out/
|
||||
dist/
|
||||
.DS_Store
|
||||
.env
|
||||
*.log
|
15
README.md
15
README.md
|
@ -1,7 +1,7 @@
|
|||
<img src="https://github.com/Sneed-Group/sneedium/blob/master/logo.png?raw=true" align="right" width="15%"/>
|
||||
|
||||
# Sneedium
|
||||
A basic web browser in Electron. ***With a 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)
|
||||
|
||||
|
@ -14,6 +14,8 @@ To clone and run this repository you'll need [Node.js](https://nodejs.org/en/dow
|
|||
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:
|
||||
|
@ -32,6 +34,17 @@ A basic Electron application needs just these files:
|
|||
- `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.
|
||||
|
||||
## Chrome Extension Support
|
||||
|
||||
Sneedium now supports loading and using Chrome extensions. Extensions are managed through a dedicated Extension Manager interface:
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
## License
|
||||
|
||||
[SPL-R5](https://github.com/Sneed-Group/sneedium/blob/master/LICENSE.md)
|
||||
|
|
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
|
||||
};
|
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>
|
|
@ -17,6 +17,7 @@
|
|||
<button onclick="back(); return false;" id="backBtn">⬅️</button>
|
||||
<button onclick="forward(); return false;" id="forwardBtn">➡️</button>
|
||||
<button id="camBtn">🎦</button>
|
||||
<button id="extBtn" title="Manage Extensions">🧩</button>
|
||||
</div>
|
||||
<div id="whProtection">
|
||||
<tab-group new-tab-button="true" sortable="true">
|
||||
|
|
240
main.js
240
main.js
|
@ -1,5 +1,5 @@
|
|||
// Modules to control application life and create native browser window
|
||||
const {app, BrowserWindow, session, ipcMain, systemPreferences} = require('electron')
|
||||
const {app, BrowserWindow, session, ipcMain, systemPreferences, dialog} = require('electron')
|
||||
const path = require('path')
|
||||
const fetch = require("cross-fetch")
|
||||
const { ElectronChromeExtensions } = require('electron-chrome-extensions')
|
||||
|
@ -8,9 +8,107 @@ const http = require('http');
|
|||
const fs = require('fs');
|
||||
const { createProxy } = require('proxy');
|
||||
const buildChromeContextMenu = require('electron-chrome-context-menu').default
|
||||
const { installCrxExtension } = require('./crx-extractor');
|
||||
var extensions
|
||||
var mic
|
||||
var cam
|
||||
|
||||
// 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 });
|
||||
}
|
||||
|
||||
// IPC handlers for extension management
|
||||
ipcMain.handle('get-extensions-list', async () => {
|
||||
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 [];
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('install-extension', async (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);
|
||||
|
||||
// If installation was successful, reload the extension
|
||||
if (installResult.success && extensions) {
|
||||
try {
|
||||
const loadResult = extensions.loadExtension(path.join(extensionsDir, installResult.extensionId));
|
||||
console.log(`Loaded extension ${installResult.extensionId}:`, loadResult);
|
||||
} catch (loadError) {
|
||||
console.error(`Error loading extension ${installResult.extensionId}:`, loadError);
|
||||
}
|
||||
}
|
||||
|
||||
return installResult;
|
||||
} catch (error) {
|
||||
console.error('Error installing extension:', error);
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('remove-extension', async (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 };
|
||||
}
|
||||
});
|
||||
|
||||
// Handle opening extension management page
|
||||
ipcMain.on('open-extensions-page', (event) => {
|
||||
createExtensionsWindow();
|
||||
});
|
||||
|
||||
app.on('web-contents-created', (event, webContents) => {
|
||||
webContents.on('context-menu', (e, params) => {
|
||||
const menu = buildChromeContextMenu({
|
||||
|
@ -25,7 +123,24 @@ app.on('web-contents-created', (event, webContents) => {
|
|||
})
|
||||
})
|
||||
|
||||
// Create an extensions 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);
|
||||
}
|
||||
|
||||
// Original IPC handlers
|
||||
ipcMain.on('windowmaker', (event, arg) => {
|
||||
createWindow();
|
||||
})
|
||||
|
@ -40,23 +155,34 @@ proxy.listen(3129)
|
|||
//Function to enable AD Blocking...
|
||||
let blocker = undefined
|
||||
async function enableGoodies(s) {
|
||||
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);
|
||||
try {
|
||||
if (!s) {
|
||||
console.log("No session provided to enableGoodies, using default session");
|
||||
s = session.defaultSession;
|
||||
}
|
||||
|
||||
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");
|
||||
} catch (error) {
|
||||
console.error("Error enabling AD blocking:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 0.0.0.0 day fix
|
||||
|
@ -71,10 +197,19 @@ function isLocal(url) {
|
|||
|
||||
function createWindow () {
|
||||
try {
|
||||
extensions = new ElectronChromeExtensions()
|
||||
} catch {
|
||||
console.log("Creating a new window in the same extension session...")
|
||||
// Initialize extensions once if not already initialized
|
||||
if (!extensions) {
|
||||
extensions = 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,
|
||||
|
@ -83,29 +218,27 @@ function createWindow () {
|
|||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
webviewTag: true,
|
||||
devTools: false,
|
||||
// Enable devTools for development
|
||||
devTools: process.env.NODE_ENV === 'development',
|
||||
nodeIntegration: false,
|
||||
sandbox: true,
|
||||
contextIsolation: true
|
||||
contextIsolation: true,
|
||||
// Enable Chrome extension support
|
||||
additionalArguments: ['--enable-features=ExtensionsToolbarMenu'],
|
||||
// Allow access to chrome:// URLs for extensions
|
||||
allowRunningInsecureContent: false,
|
||||
webSecurity: true
|
||||
}
|
||||
})
|
||||
|
||||
const homePath = app.getPath('home');
|
||||
const extensionsDir = path.join(homePath, 'SneedExtensions');
|
||||
// Load installed extensions
|
||||
loadExtensions(extensions);
|
||||
|
||||
if (!fs.existsSync(extensionsDir)) {
|
||||
fs.mkdirSync(extensionsDir, { recursive: true });
|
||||
// Register window with extensions system
|
||||
if (extensions) {
|
||||
extensions.addTab(mainWindow.webContents, mainWindow);
|
||||
}
|
||||
|
||||
const extensionFolders = fs.readdirSync(extensionsDir, { withFileTypes: true })
|
||||
.filter(dirent => dirent.isDirectory())
|
||||
.map(dirent => path.join(extensionsDir, dirent.name));
|
||||
|
||||
for (const extensionPath of extensionFolders) {
|
||||
extensions.loadExtension(extensionPath);
|
||||
}
|
||||
|
||||
extensions.addTab(mainWindow.webContents, mainWindow)
|
||||
mainWindow.setMinimumSize(1000, 300)
|
||||
|
||||
|
||||
|
@ -211,12 +344,45 @@ const regexPatterns = [
|
|||
return mainWindow;
|
||||
}
|
||||
|
||||
// Function to load all extensions from the extensions directory
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(() => {
|
||||
let x = createWindow()
|
||||
enableGoodies().then()
|
||||
enableGoodies(session.defaultSession).then(() => {
|
||||
console.log("Goodies enabled");
|
||||
}).catch(error => {
|
||||
console.error("Error enabling goodies:", error);
|
||||
});
|
||||
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
|
|
30
package-lock.json
generated
30
package-lock.json
generated
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"@ghostery/adblocker": "^2.1.1",
|
||||
"@ghostery/adblocker-electron": "^2.1.1",
|
||||
"adm-zip": "^0.5.16",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"electron-chrome-context-menu": "^1.1.0",
|
||||
"electron-chrome-extensions": "^3.10.0",
|
||||
|
@ -25,6 +26,7 @@
|
|||
"@electron-forge/maker-rpm": "^6.0.5",
|
||||
"@electron-forge/maker-squirrel": "^6.0.5",
|
||||
"@electron-forge/maker-zip": "^6.0.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^31.0.1",
|
||||
"electron-forge-maker-appimage": "^24.6.3",
|
||||
"electron-packager": "^17.1.2"
|
||||
|
@ -1350,6 +1352,15 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/adm-zip": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
|
||||
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
|
@ -2481,6 +2492,25 @@
|
|||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
||||
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "src/bin/cross-env.js",
|
||||
"cross-env-shell": "src/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
|
||||
|
|
11
package.json
11
package.json
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"name": "sneedium",
|
||||
"version": "1.0.0",
|
||||
"description": "A Electron browser",
|
||||
"description": "A Electron browser with Chrome extension support",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron-forge start",
|
||||
"package": "electron-forge package",
|
||||
"make": "npx electron-packager --platform linux,win32 --arch x64,arm64 .",
|
||||
"make-mac": "npx electron-packager --platform darwin --arch x64,arm64 ."
|
||||
"make-mac": "npx electron-packager --platform darwin --arch x64,arm64 .",
|
||||
"dev": "cross-env NODE_ENV=development electron ."
|
||||
},
|
||||
"repository": "https://github.com/Sneed-Group/sneedium",
|
||||
"keywords": [
|
||||
|
@ -15,7 +16,9 @@
|
|||
"quick",
|
||||
"start",
|
||||
"tutorial",
|
||||
"demo"
|
||||
"demo",
|
||||
"browser",
|
||||
"extension"
|
||||
],
|
||||
"author": "Sneed Group",
|
||||
"license": "SPL-R5",
|
||||
|
@ -26,6 +29,7 @@
|
|||
"@electron-forge/maker-rpm": "^6.0.5",
|
||||
"@electron-forge/maker-squirrel": "^6.0.5",
|
||||
"@electron-forge/maker-zip": "^6.0.5",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^31.0.1",
|
||||
"electron-forge-maker-appimage": "^24.6.3",
|
||||
"electron-packager": "^17.1.2"
|
||||
|
@ -33,6 +37,7 @@
|
|||
"dependencies": {
|
||||
"@ghostery/adblocker": "^2.1.1",
|
||||
"@ghostery/adblocker-electron": "^2.1.1",
|
||||
"adm-zip": "^0.5.16",
|
||||
"cross-fetch": "^3.1.5",
|
||||
"electron-chrome-context-menu": "^1.1.0",
|
||||
"electron-chrome-extensions": "^3.10.0",
|
||||
|
|
27
preload.js
27
preload.js
|
@ -10,6 +10,11 @@ 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')
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
@ -32,12 +37,24 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||
})
|
||||
//button and its event listener
|
||||
const makeWindowButton = document.getElementById('nwBtn');
|
||||
makeWindowButton.addEventListener('click', () => {
|
||||
if (makeWindowButton) {
|
||||
makeWindowButton.addEventListener('click', () => {
|
||||
ipcRenderer.send('windowmaker', 'ping')
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const camButton = document.getElementById('camBtn');
|
||||
camButton.addEventListener('click', () => {
|
||||
ipcRenderer.send('allowCam', 'ping')
|
||||
})
|
||||
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');
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue