commit 8267309a7abefa4903eb2e2c2e6886b3950a45a1 Author: Victor Giers Date: Sat Nov 8 01:51:55 2025 +0100 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6a08f8 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Bumble Electron + +A very small Electron shell that wraps the Bumble web app so you can keep it next to the rest of your desktop messengers. + +## Features +- Loads the official Bumble web experience at `https://bumble.com/get-started` inside a dedicated window. +- Blocks unexpected navigation (drag-and-drop or pop-out windows open in your default browser). +- Single-instance enforcement so app links/notifications focus the already running window. +- Pre-configured permissions for microphone, camera, geolocation, and notifications to match the needs of voice/video calls. + +## Getting Started +1. Install dependencies once: + ```bash + npm install + ``` +2. Launch the desktop app: + ```bash + npm start + ``` +3. Log in with your Bumble account right from the window that appears. + +The project uses Electron only; no build step is required. If you want the window back after closing (macOS), use the dock icon or rerun `npm start`. + +### Window behavior +- Clicking the close button now just minimizes the window so Bumble keeps running in the background. +- Use `Cmd+Q` (macOS) or `Ctrl+Q` (Windows/Linux) to quit the app completely. + +## Build a Desktop Binary +Electron Builder is preconfigured for macOS, Windows, and Linux. To create unsigned binaries for your current platform: + +```bash +npm run build +``` + +Artifacts land in `dist/`. When building on macOS without a valid signing identity (or if you need to skip signing), set `CSC_IDENTITY_AUTO_DISCOVERY=false` before running the build. + +## Packaging Notes +The default build outputs (mac `.app`, Windows installer via NSIS, Linux AppImage/Deb) are defined in `package.json#build`. Adjust that section if you need different targets or want to point at alternative icons/resources. diff --git a/bumble_icon_512.png b/bumble_icon_512.png new file mode 100644 index 0000000..83b5385 Binary files /dev/null and b/bumble_icon_512.png differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..b4e582a --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "bumble-electron", + "version": "1.0.0", + "description": "Desktop Electron shell for Bumble Web.", + "main": "src/main.js", + "scripts": { + "start": "electron .", + "dev": "electron .", + "build": "electron-builder" + }, + "keywords": [ + "electron", + "bumble", + "desktop" + ], + "author": "", + "license": "ISC", + "build": { + "appId": "com.victorstools.bumble", + "productName": "Bumble Desktop", + "mac": { + "category": "public.app-category.social-networking", + "icon": "bumble_icon_512.png" + }, + "win": { + "target": "nsis", + "icon": "bumble_icon_512.png" + }, + "linux": { + "target": [ + "AppImage", + "deb" + ], + "category": "Network", + "icon": "bumble_icon_512.png" + } + }, + "devDependencies": { + "electron": "^39.1.1", + "electron-builder": "^26.0.12" + } +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..6922000 --- /dev/null +++ b/src/main.js @@ -0,0 +1,138 @@ +const { + app, + BrowserWindow, + Menu, + shell, + session, + nativeTheme, + globalShortcut +} = require('electron'); +const path = require('path'); + +const BUMBLE_URL = 'https://bumble.com/get-started'; +const SINGLE_INSTANCE_LOCK = app.requestSingleInstanceLock(); +const ALLOWED_PERMISSIONS = new Set(['media', 'geolocation', 'notifications']); +const ICON_PATH = path.join(__dirname, '..', 'bumble_icon_512.png'); + +let mainWindow; +let isQuitting = false; + +app.setAppUserModelId('com.victorstools.bumble'); + +if (!SINGLE_INSTANCE_LOCK) { + app.quit(); +} + +const desktopUserAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome} Safari/537.36`; + +const configureSession = () => { + const defaultSession = session.defaultSession; + if (!defaultSession) { + return; + } + + defaultSession.setPermissionRequestHandler((_, permission, callback) => { + callback(ALLOWED_PERMISSIONS.has(permission)); + }); + + defaultSession.setUserAgent(desktopUserAgent); +}; + +const createWindow = () => { + if (mainWindow) { + return mainWindow; + } + + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 900, + minHeight: 600, + title: 'Bumble', + backgroundColor: nativeTheme.shouldUseDarkColors ? '#16161a' : '#ffffff', + icon: ICON_PATH, + autoHideMenuBar: true, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false, + spellcheck: true, + sandbox: false + } + }); + + mainWindow.loadURL(BUMBLE_URL, { userAgent: desktopUserAgent }); + + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + shell.openExternal(url); + return { action: 'deny' }; + }); + + mainWindow.webContents.on('will-navigate', (event, url) => { + if (!url.startsWith('https://') && !url.startsWith('http://')) { + event.preventDefault(); + } + }); + + mainWindow.on('close', event => { + if (!isQuitting) { + event.preventDefault(); + mainWindow.minimize(); + } + }); + + mainWindow.on('closed', () => { + mainWindow = null; + }); + + return mainWindow; +}; + +if (SINGLE_INSTANCE_LOCK) { + app.on('second-instance', () => { + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + mainWindow.show(); + mainWindow.focus(); + } else { + createWindow(); + } + }); +} + +app.on('before-quit', () => { + isQuitting = true; +}); + +app.whenReady().then(() => { + Menu.setApplicationMenu(null); + configureSession(); + createWindow(); + globalShortcut.register('CommandOrControl+Q', () => { + isQuitting = true; + app.quit(); + }); + + app.on('activate', () => { + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + mainWindow.show(); + } else { + createWindow(); + } + }); +}); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('will-quit', () => { + globalShortcut.unregisterAll(); +}); diff --git a/src/preload.js b/src/preload.js new file mode 100644 index 0000000..a033378 --- /dev/null +++ b/src/preload.js @@ -0,0 +1,19 @@ +const { contextBridge } = require('electron'); + +// Prevent files from being dropped into the Bumble webview, which would otherwise +// trigger navigations away from the Bumble UI. +window.addEventListener('dragover', event => { + event.preventDefault(); +}); + +window.addEventListener('drop', event => { + event.preventDefault(); +}); + +contextBridge.exposeInMainWorld('bumbleDesktop', { + platform: process.platform, + versions: { + chrome: process.versions.chrome, + electron: process.versions.electron + } +});