Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Your companion browser for Netflix, Twitter(X), and everything in between.**

🌐 **[Visit our website](https://browser.aka.page)** | 📚 **[Technical Wiki](https://deepwiki.com/hmmhmmhm/aka-browser)** | 🚀 **Currently in Beta** — Stable Release coming in November!
🌐 **[Visit our website](https://browser.aka.page)** | 📚 **[Technical Wiki](https://deepwiki.com/hmmhmmhm/aka-browser)** | 🚀 **Stage-0 Beta** — Stable release schedule will be announced after beta validation.

<img src="https://i.imgur.com/YhQkOP5.png" alt="aka-browser screenshot" width="600"/>

Expand Down
25 changes: 14 additions & 11 deletions apps/browser/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"evs:setup": "bash scripts/setup-evs.sh",
"evs:verify": "node scripts/evs-sign.js --verify",
"check-types": "tsc --noEmit",
"lint": "tsc --noEmit --pretty false",
"audit": "npm audit",
"audit:fix": "npm audit fix",
"outdated": "npm outdated"
Expand All @@ -32,26 +33,28 @@
"author": "hmmhmmhm",
"license": "MIT",
"devDependencies": {
"@electron/notarize": "^2.5.0",
"@tailwindcss/vite": "^4.1.14",
"@electron/notarize": "^3.1.1",
"@tailwindcss/vite": "^4.3.0",
"@types/node": "^20.11.0",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.2",
"@vitejs/plugin-react": "^5.0.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"concurrently": "^8.2.2",
"cross-env": "^10.1.0",
"dotenv": "^17.2.3",
"electron": "github:castlabs/electron-releases#v38.0.0+wvcus",
"electron-builder": "^26.0.12",
"tailwindcss": "^4.1.14",
"dotenv": "^17.4.2",
"electron": "github:castlabs/electron-releases#v39.8.10+wvcus",
"electron-builder": "^26.8.1",
"electron-builder-squirrel-windows": "26.8.1",
"esbuild": "^0.28.0",
"tailwindcss": "^4.3.0",
"typescript": "5.9.2",
"vite": "^7.1.11"
"vite": "^8.0.13"
},
"build": {
"appId": "com.aka-browser.app",
"productName": "aka-browser",
"electronDist": "node_modules/electron/dist",
"electronVersion": "38.0.0",
"electronVersion": "39.8.10",
"npmRebuild": false,
"asar": true,
"asarUnpack": [
Expand Down
15 changes: 14 additions & 1 deletion apps/browser/src/main/app-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
* Application lifecycle management
*/

import { app, BrowserWindow, nativeImage, components } from "electron";
import { app, BrowserWindow, nativeImage, components, session } from "electron";
import path from "path";
import { AppState } from "./types";
import { WindowManager } from "./window-manager";
import { TrayManager } from "./tray-manager";
import { toAcceptLanguage } from "../shared/language";

export class AppLifecycle {
private state: AppState;
Expand Down Expand Up @@ -89,6 +90,8 @@ export class AppLifecycle {
"[Widevine] Using castlabs electron-releases with built-in Widevine CDM"
);

this.configureLanguageHeaders();

// Set dock icon for macOS
if (process.platform === "darwin") {
const iconPath = path.join(__dirname, "../assets/icon.png");
Expand Down Expand Up @@ -125,4 +128,14 @@ export class AppLifecycle {
this.trayManager.destroy();
});
}

private configureLanguageHeaders(): void {
const webSession = session.fromPartition("persist:main");
webSession.webRequest.onBeforeSendHeaders((details, callback) => {
details.requestHeaders["Accept-Language"] = toAcceptLanguage(
this.state.language
);
callback({ requestHeaders: details.requestHeaders });
});
}
}
9 changes: 8 additions & 1 deletion apps/browser/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import { FaviconCache } from "./favicon-cache";
import { IPCHandlers } from "./ipc-handlers";
import { TrayManager } from "./tray-manager";
import { AppLifecycle } from "./app-lifecycle";
import { LanguageManager } from "./language-manager";
import { toChromiumLocale } from "../shared/language";

const languageManager = new LanguageManager();
const initialLanguage = languageManager.getState().effectiveLanguage;
app.commandLine.appendSwitch("lang", toChromiumLocale(initialLanguage));

// Initialize application state
const appState: AppState = {
Expand All @@ -23,6 +29,7 @@ const appState: AppState = {
tabs: [],
activeTabId: null,
latestThemeColor: null,
language: initialLanguage,
};

// Initialize managers
Expand All @@ -32,7 +39,7 @@ const faviconCache = new FaviconCache();
const tabManager = new TabManager(appState, themeColorCache);
const windowManager = new WindowManager(appState, tabManager);
const trayManager = new TrayManager(appState, windowManager);
const ipcHandlers = new IPCHandlers(appState, tabManager, windowManager, bookmarkManager, faviconCache, themeColorCache);
const ipcHandlers = new IPCHandlers(appState, tabManager, windowManager, bookmarkManager, faviconCache, themeColorCache, languageManager);
const appLifecycle = new AppLifecycle(appState, windowManager, trayManager);

// Initialize Widevine
Expand Down
115 changes: 20 additions & 95 deletions apps/browser/src/main/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import { BookmarkManager } from "./bookmark-manager";
import { FaviconCache } from "./favicon-cache";
import { isValidUrl, sanitizeUrl, getUserAgentForUrl, logSecurityEvent } from "./security";
import { ThemeColorCache } from "./theme-cache";
import {
registerBookmarkHandlers,
registerFaviconHandlers,
} from "./ipc/bookmark-favicon-handlers";
import { LanguageManager } from "./language-manager";

export class IPCHandlers {
private state: AppState;
Expand All @@ -18,21 +23,24 @@ export class IPCHandlers {
private bookmarkManager: BookmarkManager;
private faviconCache: FaviconCache;
private themeColorCache: ThemeColorCache;
private languageManager: LanguageManager;

constructor(
state: AppState,
tabManager: TabManager,
windowManager: WindowManager,
bookmarkManager: BookmarkManager,
faviconCache: FaviconCache,
themeColorCache: ThemeColorCache
themeColorCache: ThemeColorCache,
languageManager: LanguageManager
) {
this.state = state;
this.tabManager = tabManager;
this.windowManager = windowManager;
this.bookmarkManager = bookmarkManager;
this.faviconCache = faviconCache;
this.themeColorCache = themeColorCache;
this.languageManager = languageManager;
}

/**
Expand All @@ -45,8 +53,8 @@ export class IPCHandlers {
this.registerThemeHandlers();
this.registerOrientationHandlers();
this.registerAppHandlers();
this.registerBookmarkHandlers();
this.registerFaviconHandlers();
registerBookmarkHandlers(this.state, this.bookmarkManager);
registerFaviconHandlers(this.faviconCache);
}

/**
Expand Down Expand Up @@ -385,104 +393,21 @@ export class IPCHandlers {
ipcMain.handle("get-app-version", () => {
return app.getVersion();
});
}

/**
* Notify all windows about bookmark updates
*/
private notifyBookmarkUpdate(): void {
// Notify main window
if (this.state.mainWindow && !this.state.mainWindow.isDestroyed()) {
this.state.mainWindow.webContents.send("bookmarks-updated");
}

// Notify WebContentsView
if (this.state.webContentsView && !this.state.webContentsView.webContents.isDestroyed()) {
this.state.webContentsView.webContents.send("bookmarks-updated");
}
}

/**
* Register bookmark management handlers
*/
private registerBookmarkHandlers(): void {
// Get all bookmarks
ipcMain.handle("bookmarks-get-all", () => {
return this.bookmarkManager.getAll();
});

// Get bookmark by ID
ipcMain.handle("bookmarks-get-by-id", (_event, id: string) => {
return this.bookmarkManager.getById(id);
});

// Check if URL is bookmarked
ipcMain.handle("bookmarks-is-bookmarked", (_event, url: string) => {
return this.bookmarkManager.isBookmarked(url);
});

// Add bookmark
ipcMain.handle("bookmarks-add", (_event, title: string, url: string, favicon?: string) => {
const bookmark = this.bookmarkManager.add(title, url, favicon);
this.notifyBookmarkUpdate();
return bookmark;
});

// Update bookmark
ipcMain.handle("bookmarks-update", (_event, id: string, updates: any) => {
const bookmark = this.bookmarkManager.update(id, updates);
this.notifyBookmarkUpdate();
return bookmark;
ipcMain.handle("get-language-state", () => {
return this.languageManager.getState();
});

// Remove bookmark
ipcMain.handle("bookmarks-remove", (_event, id: string) => {
const result = this.bookmarkManager.remove(id);
this.notifyBookmarkUpdate();
return result;
});
ipcMain.handle("set-preferred-language", (_event, language: any) => {
const nextState = this.languageManager.setPreferredLanguage(language);
this.state.language = nextState.effectiveLanguage;

// Remove bookmark by URL
ipcMain.handle("bookmarks-remove-by-url", (_event, url: string) => {
const result = this.bookmarkManager.removeByUrl(url);
this.notifyBookmarkUpdate();
return result;
});
if (this.state.mainWindow && !this.state.mainWindow.isDestroyed()) {
this.state.mainWindow.webContents.send("language-changed", nextState);
}

// Clear all bookmarks
ipcMain.handle("bookmarks-clear", () => {
this.bookmarkManager.clear();
this.notifyBookmarkUpdate();
return nextState;
});
}

/**
* Register favicon cache handlers
*/
private registerFaviconHandlers(): void {
// Get favicon with caching
ipcMain.handle("favicon-get", async (_event, url: string) => {
return this.faviconCache.getFavicon(url);
});

// Get favicon with fallback sources
ipcMain.handle("favicon-get-with-fallback", async (_event, pageUrl: string) => {
return this.faviconCache.getFaviconWithFallback(pageUrl);
});

// Check if favicon is cached
ipcMain.handle("favicon-is-cached", (_event, url: string) => {
return this.faviconCache.isCached(url);
});

// Clear favicon cache
ipcMain.handle("favicon-clear-cache", () => {
this.faviconCache.clearCache();
});

// Get cache size
ipcMain.handle("favicon-get-cache-size", () => {
return this.faviconCache.getCacheSize();
});
}
}
77 changes: 77 additions & 0 deletions apps/browser/src/main/ipc/bookmark-favicon-handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ipcMain, WebContentsView, BrowserWindow } from "electron";
import { BookmarkManager } from "../bookmark-manager";
import { FaviconCache } from "../favicon-cache";

interface BookmarkHandlersState {
mainWindow: BrowserWindow | null;
webContentsView: WebContentsView | null;
}

export function registerBookmarkHandlers(
state: BookmarkHandlersState,
bookmarkManager: BookmarkManager
): void {
const notifyBookmarkUpdate = () => {
if (state.mainWindow && !state.mainWindow.isDestroyed()) {
state.mainWindow.webContents.send("bookmarks-updated");
}

if (
state.webContentsView &&
!state.webContentsView.webContents.isDestroyed()
) {
state.webContentsView.webContents.send("bookmarks-updated");
}
};

ipcMain.handle("bookmarks-get-all", () => bookmarkManager.getAll());
ipcMain.handle("bookmarks-get-by-id", (_event, id: string) =>
bookmarkManager.getById(id)
);
ipcMain.handle("bookmarks-is-bookmarked", (_event, url: string) =>
bookmarkManager.isBookmarked(url)
);
ipcMain.handle(
"bookmarks-add",
(_event, title: string, url: string, favicon?: string) => {
const bookmark = bookmarkManager.add(title, url, favicon);
notifyBookmarkUpdate();
return bookmark;
}
);
ipcMain.handle("bookmarks-update", (_event, id: string, updates: any) => {
const bookmark = bookmarkManager.update(id, updates);
notifyBookmarkUpdate();
return bookmark;
});
ipcMain.handle("bookmarks-remove", (_event, id: string) => {
const result = bookmarkManager.remove(id);
notifyBookmarkUpdate();
return result;
});
ipcMain.handle("bookmarks-remove-by-url", (_event, url: string) => {
const result = bookmarkManager.removeByUrl(url);
notifyBookmarkUpdate();
return result;
});
ipcMain.handle("bookmarks-clear", () => {
bookmarkManager.clear();
notifyBookmarkUpdate();
});
}

export function registerFaviconHandlers(faviconCache: FaviconCache): void {
ipcMain.handle("favicon-get", async (_event, url: string) =>
faviconCache.getFavicon(url)
);
ipcMain.handle("favicon-get-with-fallback", async (_event, pageUrl: string) =>
faviconCache.getFaviconWithFallback(pageUrl)
);
ipcMain.handle("favicon-is-cached", (_event, url: string) =>
faviconCache.isCached(url)
);
ipcMain.handle("favicon-clear-cache", () => {
faviconCache.clearCache();
});
ipcMain.handle("favicon-get-cache-size", () => faviconCache.getCacheSize());
}
Loading