Skip to content
Open
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 electron/renderer/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export const updateAccountBadgeCount = (id: string, count: number) => {
}, 0);
const ignoreFlash = account?.availability === Availability.Type.BUSY;

window.sendBadgeCount(accumulatedCount, ignoreFlash);
window.electronAPI.sendBadgeCount(accumulatedCount, ignoreFlash);

if (account) {
const countHasChanged = account.badgeCount !== count;
Expand Down
42 changes: 30 additions & 12 deletions electron/renderer/src/components/WebView/Webview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
} from '../../actions';
import {accountAction} from '../../actions/AccountAction';
import {State} from '../../index';
import {getText, wrapperLocale} from '../../lib/locale';
import {getText, getWrapperLocale} from '../../lib/locale';
import {WindowUrl} from '../../lib/WindowUrl';
import {AccountSelector} from '../../selector/AccountSelector';
import {Account, ConversationJoinData} from '../../types/account';
Expand All @@ -58,7 +58,7 @@ const getEnvironmentUrl = (account: Account) => {
url.searchParams.set('id', account.id);

// set the current language
url.searchParams.set('hl', wrapperLocale);
url.searchParams.set('hl', getWrapperLocale());

if (account.ssoCode && account.isAdding) {
url.pathname = '/auth';
Expand Down Expand Up @@ -169,11 +169,12 @@ const Webview = ({
setWebviewError(error);
}
};
webviewRef.current?.addEventListener(ON_WEBVIEW_ERROR, listener);
const webview = webviewRef.current;
webview?.addEventListener(ON_WEBVIEW_ERROR, listener);

return () => {
if (webviewRef.current) {
webviewRef.current.removeEventListener(ON_WEBVIEW_ERROR, listener);
if (webview) {
webview.removeEventListener(ON_WEBVIEW_ERROR, listener);
}
};
}, [webviewRef, account]);
Expand Down Expand Up @@ -211,7 +212,7 @@ const Webview = ({
case EVENT_TYPE.LIFECYCLE.SIGNED_IN: {
if (conversationJoinData) {
const {code, key, domain} = conversationJoinData;
window.sendConversationJoinToHost(accountId, code, key, domain);
window.electronAPI.sendConversationJoinToHost(accountId, code, key, domain);
setConversationJoinData(accountId, undefined);
}
updateAccountLifecycle(accountId, channel);
Expand Down Expand Up @@ -241,7 +242,7 @@ const Webview = ({

if (isConversationJoinData(data)) {
if (accountLifecycle === EVENT_TYPE.LIFECYCLE.SIGNED_IN) {
window.sendConversationJoinToHost(accountId, data.code, data.key, data.domain);
window.electronAPI.sendConversationJoinToHost(accountId, data.code, data.key, data.domain);
setConversationJoinData(accountId, undefined);
} else {
setConversationJoinData(accountId, data);
Expand Down Expand Up @@ -272,20 +273,37 @@ const Webview = ({
}
};

webviewRef.current?.addEventListener(ON_IPC_MESSAGE, onIpcMessage);
const webview = webviewRef.current;
webview?.addEventListener(ON_IPC_MESSAGE, onIpcMessage);

return () => {
if (webviewRef.current) {
webviewRef.current.removeEventListener(ON_IPC_MESSAGE, onIpcMessage);
if (webview) {
webview.removeEventListener(ON_IPC_MESSAGE, onIpcMessage);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [account, accountLifecycle, conversationJoinData]);

const deleteWebview = (account: Account) => {
window.sendDeleteAccount(account.id, account.sessionID).then(() => {
// For accounts being added (no webview yet), just abort creation directly
const accountWebview = document.querySelector<Electron.WebviewTag>(`.Webview[data-accountid="${account.id}"]`);
if (!accountWebview) {
// Webview doesn't exist yet (account being added), just abort creation
abortAccountCreation(account.id);
});
return;
}

// For existing accounts, delete the webview data first
window.electronAPI
.sendDeleteAccount(account.id, account.sessionID)
.then(() => {
abortAccountCreation(account.id);
})
.catch(error => {
console.error('Failed to delete account:', error);
// Still abort account creation even if deletion fails
abortAccountCreation(account.id);
});
Comment on lines +302 to +306
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling logs to console.error but provides no user feedback. When account deletion fails, the user should be notified through the UI (e.g., a toast or dialog) rather than silently failing with only a console log. This is important for user experience as they may not know the operation failed.

Copilot generated this review using guidance from repository custom instructions.
};

return (
Expand Down
15 changes: 11 additions & 4 deletions electron/renderer/src/components/context/EditAccountMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const EditAccountMenu = ({
<ContextMenuItem
onClick={() => {
connected.switchWebview(accountIndex);
window.sendLogoutAccount(accountId);
window.electronAPI.sendLogoutAccount(accountId);
}}
>
{getText('wrapperLogOut')}
Expand All @@ -73,9 +73,16 @@ const EditAccountMenu = ({

<ContextMenuItem
onClick={() => {
window.sendDeleteAccount(accountId, sessionID).then(() => {
connected.abortAccountCreation(accountId);
});
window.electronAPI
.sendDeleteAccount(accountId, sessionID)
.then(() => {
connected.abortAccountCreation(accountId);
})
.catch(error => {
console.error('Failed to delete account:', error);
// Still abort account creation even if deletion fails
connected.abortAccountCreation(accountId);
});
Comment on lines +81 to +85
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling logs to console.error but provides no user feedback. When account deletion fails, the user should be notified through the UI (e.g., a toast or dialog) rather than silently failing with only a console log. This is important for user experience as they may not know the operation failed.

Copilot generated this review using guidance from repository custom instructions.
}}
>
{getText('wrapperRemoveAccount')}
Expand Down
4 changes: 2 additions & 2 deletions electron/renderer/src/lib/WindowUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
*/

import {wrapperLocale} from './locale';
import {getWrapperLocale} from './locale';

export class WindowUrl {
static createWebAppUrl(localRendererUrl: URL | string, customBackendUrl: string) {
Expand All @@ -32,7 +32,7 @@ export class WindowUrl {
});

// set the current language
envUrlParams.set('hl', wrapperLocale);
envUrlParams.set('hl', getWrapperLocale());

return customBackendUrlParsed.href;
}
Expand Down
49 changes: 43 additions & 6 deletions electron/renderer/src/lib/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,41 @@
*
*/

import {i18nLanguageIdentifier} from '../../../src/locale';
import {i18nLanguageIdentifier, SupportedI18nLanguage} from '../../../src/locale';

window.locStrings = window.locStrings || {};
window.locStringsDefault = window.locStringsDefault || {};
window.locale = window.locale || 'en';
// Get locale info from electronAPI (contextBridge)
// Made lazy to avoid accessing window.electronAPI before preload script has executed
const getLocaleInfo = () => {
if (window.electronAPI) {
return {
strings: window.electronAPI.locale.strings,
stringsDefault: window.electronAPI.locale.stringsDefault,
current: window.electronAPI.locale.current,
};
}
// Fallback for development/testing or when electronAPI isn't available yet
return {
strings: {},
stringsDefault: {},
current: 'en' as SupportedI18nLanguage,
};
};

// Cache locale info once it's available
let localeInfoCache: ReturnType<typeof getLocaleInfo> | null = null;

const getCachedLocaleInfo = () => {
if (!localeInfoCache) {
localeInfoCache = getLocaleInfo();
}
return localeInfoCache;
};

export const getText = (stringIdentifier: i18nLanguageIdentifier, paramReplacements?: Record<string, string>) => {
let str = window.locStrings[stringIdentifier] || window.locStringsDefault[stringIdentifier] || stringIdentifier;
const localeInfo = getCachedLocaleInfo();
const strings = localeInfo.strings as Record<string, string>;
const stringsDefault = localeInfo.stringsDefault as Record<string, string>;
let str = strings[stringIdentifier] || stringsDefault[stringIdentifier] || stringIdentifier;

const replacements = {...paramReplacements};
for (const replacement of Object.keys(replacements)) {
Expand All @@ -37,4 +64,14 @@ export const getText = (stringIdentifier: i18nLanguageIdentifier, paramReplaceme
return str;
};

export const wrapperLocale = window.locale;
// getWrapperLocale is used in WindowUrl.ts and Webview.tsx
// Use a getter function to always get the current locale value
// Electron guarantees preload scripts execute before renderer code,
// so window.electronAPI should always be available
export const getWrapperLocale = (): SupportedI18nLanguage => {
if (window.electronAPI) {
return window.electronAPI.locale.current;
}
// Fallback for development/testing or edge cases
return 'en' as SupportedI18nLanguage;
};
2 changes: 1 addition & 1 deletion electron/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ const showMainWindow = async (mainWindowState: windowStateKeeper.State): Promise
title: config.name,
webPreferences: {
backgroundThrottling: false,
contextIsolation: false,
contextIsolation: true,
nodeIntegration: false,
preload: PRELOAD_JS,
sandbox: false,
Expand Down
19 changes: 15 additions & 4 deletions electron/src/preload/menu/preload-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,28 +70,39 @@ const createDefaultMenu = (copyContext: string) =>

const createTextMenu = (params: ContextMenuParams, webContents: WebContents): ElectronMenu => {
const {editFlags, dictionarySuggestions} = params;
// Detect if context menu is triggered from a webview
const isWebview = webContents.getType() === 'webview';
const webContentsId = webContents.id;

const template: MenuItemConstructorOptions[] = [
{
click: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.CUT),
click: isWebview
? () => webContents.cut()
: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.CUT, webContentsId),
enabled: editFlags.canCut,
label: locale.getText('menuCut'),
},
{
click: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.COPY),
click: isWebview
? () => webContents.copy()
: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.COPY, webContentsId),
enabled: editFlags.canCopy,
label: locale.getText('menuCopy'),
},
{
click: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.PASTE),
click: isWebview
? () => webContents.paste()
: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.PASTE, webContentsId),
enabled: editFlags.canPaste,
label: locale.getText('menuPaste'),
},
{
type: 'separator',
},
{
click: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.SELECT_ALL),
click: isWebview
? () => webContents.selectAll()
: (_menuItem, baseWindow) => sendToWebContents(baseWindow, EVENT_TYPE.EDIT.SELECT_ALL, webContentsId),
enabled: editFlags.canSelectAll,
label: locale.getText('menuSelectAll'),
},
Expand Down
Loading