Electron: Load application from URL (#3774)

Electron: safe fallback for missed background image in cache (#3883)
This commit is contained in:
Alexander Zinchuk 2023-09-25 12:59:53 +02:00
parent af69e04e31
commit 3cd9192c1d
44 changed files with 356 additions and 135 deletions

View File

@ -57,6 +57,10 @@ jobs:
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k actions $KEY_CHAIN
security find-identity -v -p codesigning $KEY_CHAIN
- name: Get branch name for current workflow run
id: branch-name
uses: tj-actions/branch-names@v7
- name: Build, package and publish
env:
TELEGRAM_API_ID: ${{ secrets.TELEGRAM_API_ID }}
@ -67,6 +71,8 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PUBLISH_REPO: ${{ vars.PUBLISH_REPO }}
BASE_URL: ${{ vars.BASE_URL }}
IS_PREVIEW: ${{ steps.branch-name.outputs.current_branch != 'master' }}
run: |
if [ -z "$PUBLISH_REPO" ]; then
npm run electron:package:staging

View File

@ -11,9 +11,9 @@
"build:mocked": "cross-env APP_ENV=test APP_MOCKED_CLIENT=1 npm run build:dev",
"build:production": "webpack && bash ./deploy/copy_to_dist.sh",
"web:release:production": "npm run update_version && npm i && npm run build:production && git add -A && git commit -a -m '[Build]' --no-verify && git push",
"electron:dev": "npm run electron:webpack && IS_ELECTRON=true concurrently -n main,renderer,electron \"npm run electron:webpack -- --watch\" \"npm run dev\" \"electronmon dist/electron\"",
"electron:dev": "npm run electron:webpack && IS_ELECTRON_BUILD=true concurrently -n main,renderer,electron \"npm run electron:webpack -- --watch\" \"npm run dev\" \"electronmon dist/electron\"",
"electron:webpack": "cross-env APP_ENV=$ENV webpack --config ./webpack-electron.config.ts",
"electron:build": "IS_ELECTRON=true npm run build:$ENV && electron-builder install-app-deps && electron-rebuild && ENV=$ENV npm run electron:webpack",
"electron:build": "IS_ELECTRON_BUILD=true npm run build:$ENV && electron-builder install-app-deps && electron-rebuild && ENV=$ENV npm run electron:webpack",
"electron:package": "npm run electron:build && npx rimraf dist-electron && electron-builder build --win --mac --linux --config src/electron/config.yml",
"electron:package:staging": "ENV=staging npm run electron:package -- -p never",
"electron:release:production": "ENV=production npm run electron:package -- -p always",

View File

@ -0,0 +1 @@
10.0.0

View File

@ -6,13 +6,13 @@ import React, {
import type RLottieInstance from '../../lib/rlottie/RLottie';
import { IS_ELECTRON } from '../../config';
import { requestMeasure } from '../../lib/fasterdom/fasterdom';
import { ensureRLottie, getRLottie } from '../../lib/rlottie/RLottie.async';
import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import generateUniqueId from '../../util/generateUniqueId';
import { hexToRgb } from '../../util/switchTheme';
import { IS_ELECTRON } from '../../util/windowEnvironment';
import useColorFilter from '../../hooks/stickers/useColorFilter';
import useBackgroundMode, { isBackgroundModeActive } from '../../hooks/useBackgroundMode';

View File

@ -4,7 +4,7 @@ import React from '../../../lib/teact/teact';
import type { TextPart } from '../../../types';
import {
IS_ELECTRON, PRODUCTION_URL, RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE,
BASE_URL, IS_ELECTRON_BUILD, RE_LINK_TEMPLATE, RE_MENTION_TEMPLATE,
} from '../../../config';
import EMOJI_REGEX from '../../../lib/twemojiRegex';
import buildClassName from '../../../util/buildClassName';
@ -112,7 +112,8 @@ function replaceEmojis(textParts: TextPart[], size: 'big' | 'small', type: 'jsx'
if (!code) {
emojiResult.push(emoji);
} else {
const src = `${IS_ELECTRON ? PRODUCTION_URL : '.'}/img-apple-${size === 'big' ? '160' : '64'}/${code}.png`;
const baseSrcUrl = IS_ELECTRON_BUILD ? BASE_URL : '.';
const src = `${baseSrcUrl}/img-apple-${size === 'big' ? '160' : '64'}/${code}.png`;
const className = buildClassName(
'emoji',
size === 'small' && 'emoji-small',

View File

@ -3,9 +3,9 @@ import { getActions } from '../../../global';
import type { ActiveEmojiInteraction } from '../../../global/types';
import { IS_ELECTRON } from '../../../config';
import buildStyle from '../../../util/buildStyle';
import safePlay from '../../../util/safePlay';
import { IS_ELECTRON } from '../../../util/windowEnvironment';
import { REM } from '../helpers/mediaDimensions';
import useLastCallback from '../../../hooks/useLastCallback';

View File

@ -40,7 +40,8 @@ type StateProps = {
nextSettingsScreen?: SettingsScreens;
nextFoldersAction?: ReducerAction<FoldersActions>;
isChatOpen: boolean;
isUpdateAvailable?: boolean;
isAppUpdateAvailable?: boolean;
isElectronUpdateAvailable?: boolean;
isForumPanelOpen?: boolean;
forumPanelChatId?: string;
isClosingSearch?: boolean;
@ -73,7 +74,8 @@ function LeftColumn({
nextSettingsScreen,
nextFoldersAction,
isChatOpen,
isUpdateAvailable,
isAppUpdateAvailable,
isElectronUpdateAvailable,
isForumPanelOpen,
forumPanelChatId,
isClosingSearch,
@ -491,7 +493,8 @@ function LeftColumn({
onSettingsScreenSelect={handleSettingsScreenSelect}
onReset={handleReset}
shouldSkipTransition={shouldSkipHistoryAnimations}
isUpdateAvailable={isUpdateAvailable}
isAppUpdateAvailable={isAppUpdateAvailable}
isElectronUpdateAvailable={isElectronUpdateAvailable}
isForumPanelOpen={isForumPanelOpen}
onTopicSearch={handleTopicSearch}
/>
@ -537,7 +540,8 @@ export default memo(withGlobal<OwnProps>(
passcode: {
hasPasscode,
},
isUpdateAvailable,
isAppUpdateAvailable,
isElectronUpdateAvailable,
archiveSettings,
} = global;
@ -556,7 +560,8 @@ export default memo(withGlobal<OwnProps>(
nextSettingsScreen,
nextFoldersAction,
isChatOpen,
isUpdateAvailable,
isAppUpdateAvailable,
isElectronUpdateAvailable,
isForumPanelOpen,
forumPanelChatId,
isClosingSearch: tabState.globalSearch.isClosing,

View File

@ -8,7 +8,7 @@ import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReduc
import type { SettingsScreens } from '../../../types';
import { LeftColumnContent } from '../../../types';
import { IS_ELECTRON } from '../../../config';
import { PRODUCTION_URL } from '../../../config';
import buildClassName from '../../../util/buildClassName';
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
@ -35,7 +35,8 @@ type OwnProps = {
contactsFilter: string;
shouldSkipTransition?: boolean;
foldersDispatch: FolderEditDispatch;
isUpdateAvailable?: boolean;
isAppUpdateAvailable?: boolean;
isElectronUpdateAvailable?: boolean;
isForumPanelOpen?: boolean;
isClosingSearch?: boolean;
onSearchQuery: (query: string) => void;
@ -58,7 +59,8 @@ const LeftMain: FC<OwnProps> = ({
contactsFilter,
shouldSkipTransition,
foldersDispatch,
isUpdateAvailable,
isAppUpdateAvailable,
isElectronUpdateAvailable,
isForumPanelOpen,
onSearchQuery,
onContentChange,
@ -68,6 +70,11 @@ const LeftMain: FC<OwnProps> = ({
}) => {
const { closeForumPanel } = getActions();
const [isNewChatButtonShown, setIsNewChatButtonShown] = useState(IS_TOUCH_ENV);
const [isElectronAutoUpdateEnabled, setIsElectronAutoUpdateEnabled] = useState(false);
useEffect(() => {
window.electron?.getIsAutoUpdateEnabled().then(setIsElectronAutoUpdateEnabled);
}, []);
const {
shouldRenderForumPanel, handleForumPanelAnimationEnd,
@ -79,7 +86,7 @@ const LeftMain: FC<OwnProps> = ({
const {
shouldRender: shouldRenderUpdateButton,
transitionClassNames: updateButtonClassNames,
} = useShowTransition(isUpdateAvailable);
} = useShowTransition(isAppUpdateAvailable || isElectronUpdateAvailable);
const isMouseInside = useRef(false);
@ -120,7 +127,9 @@ const LeftMain: FC<OwnProps> = ({
});
const handleUpdateClick = useLastCallback(() => {
if (IS_ELECTRON) {
if (!isElectronAutoUpdateEnabled) {
window.open(`${PRODUCTION_URL}/get`, '_blank', 'noopener');
} else if (isElectronUpdateAvailable) {
window.electron?.installUpdate();
} else {
window.location.reload();
@ -214,6 +223,7 @@ const LeftMain: FC<OwnProps> = ({
<Button
fluid
pill
color={isElectronAutoUpdateEnabled ? 'primary' : 'green'}
className={buildClassName('btn-update', updateButtonClassNames)}
onClick={handleUpdateClick}
>

View File

@ -17,7 +17,6 @@ import {
DEBUG,
FEEDBACK_URL,
IS_BETA,
IS_ELECTRON,
IS_TEST,
PRODUCTION_HOSTNAME,
WEB_VERSION_BASE,
@ -39,7 +38,7 @@ import captureEscKeyListener from '../../../util/captureEscKeyListener';
import { formatDateToString } from '../../../util/dateFormat';
import { getPromptInstall } from '../../../util/installPrompt';
import { switchPermanentWebVersion } from '../../../util/permanentWebVersion';
import { IS_APP, IS_MAC_OS } from '../../../util/windowEnvironment';
import { IS_APP, IS_ELECTRON, IS_MAC_OS } from '../../../util/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout';
import useConnectionStatus from '../../../hooks/useConnectionStatus';

View File

@ -1,5 +1,7 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useEffect, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { DEBUG_LOG_FILENAME } from '../../../config';
@ -40,6 +42,11 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
const { requestConfetti, setSettingOption } = getActions();
const lang = useLang();
const [isAutoUpdateEnabled, setIsAutoUpdateEnabled] = useState(false);
useEffect(() => {
window.electron?.getIsAutoUpdateEnabled().then(setIsAutoUpdateEnabled);
}, []);
useHistoryBack({
isActive,
onBack: onReset,
@ -51,6 +58,10 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
download(url, DEBUG_LOG_FILENAME);
});
const handleIsAutoUpdateEnabledChange = useCallback((isChecked: boolean) => {
window.electron?.setIsAutoUpdateEnabled(isChecked);
}, []);
return (
<div className="settings-content custom-scroll">
<div className="settings-content-header no-border">
@ -108,6 +119,12 @@ const SettingsExperimental: FC<OwnProps & StateProps> = ({
onCheck={() => setSettingOption({ shouldDebugExportedSenders: !shouldDebugExportedSenders })}
/>
<Checkbox
label="Enable autoupdates"
checked={Boolean(isAutoUpdateEnabled)}
onCheck={handleIsAutoUpdateEnabledChange}
/>
<ListItem
// eslint-disable-next-line react/jsx-no-bind
onClick={handleDownloadLog}

View File

@ -8,12 +8,11 @@ import type { ISettings, TimeFormat } from '../../../types';
import type { IRadioOption } from '../../ui/RadioGroup';
import { SettingsScreens } from '../../../types';
import { IS_ELECTRON } from '../../../config';
import { pick } from '../../../util/iteratees';
import { setTimeFormat } from '../../../util/langProvider';
import { getSystemTheme } from '../../../util/systemTheme';
import {
IS_ANDROID, IS_IOS, IS_MAC_OS, IS_WINDOWS,
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_MAC_OS, IS_WINDOWS,
} from '../../../util/windowEnvironment';
import useAppLayout from '../../../hooks/useAppLayout';

View File

@ -21,7 +21,7 @@ import type { LangCode } from '../../types';
import { ElectronEvent } from '../../types/electron';
import {
BASE_EMOJI_KEYWORD_LANG, DEBUG, INACTIVE_MARKER, IS_ELECTRON,
BASE_EMOJI_KEYWORD_LANG, DEBUG, INACTIVE_MARKER,
} from '../../config';
import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
import { getUserFullName } from '../../global/helpers';
@ -47,7 +47,7 @@ import { processDeepLink } from '../../util/deeplink';
import { Bundles, loadBundle } from '../../util/moduleLoader';
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
import updateIcon from '../../util/updateIcon';
import { IS_ANDROID } from '../../util/windowEnvironment';
import { IS_ANDROID, IS_ELECTRON } from '../../util/windowEnvironment';
import useAppLayout from '../../hooks/useAppLayout';
import useBackgroundMode from '../../hooks/useBackgroundMode';
@ -252,7 +252,7 @@ const Main: FC<OwnProps & StateProps> = ({
loadTopReactions,
loadRecentReactions,
loadFeaturedEmojiStickers,
setIsAppUpdateAvailable,
setIsElectronUpdateAvailable,
loadPremiumSetStickers,
} = getActions();
@ -283,25 +283,25 @@ const Main: FC<OwnProps & StateProps> = ({
}
}, [isDesktop, isLeftColumnOpen, isMiddleColumnOpen, isMobile, toggleLeftColumn]);
useInterval(checkAppVersion, (isMasterTab && !IS_ELECTRON) ? APP_OUTDATED_TIMEOUT_MS : undefined, true);
useInterval(checkAppVersion, isMasterTab ? APP_OUTDATED_TIMEOUT_MS : undefined, true);
useEffect(() => {
if (!IS_ELECTRON) {
return undefined;
}
const removeUpdateDownloadedListener = window.electron!.on(ElectronEvent.UPDATE_DOWNLOADED, () => {
setIsAppUpdateAvailable(true);
const removeUpdateAvailableListener = window.electron!.on(ElectronEvent.UPDATE_AVAILABLE, () => {
setIsElectronUpdateAvailable(true);
});
const removeUpdateErrorListener = window.electron!.on(ElectronEvent.UPDATE_ERROR, () => {
setIsAppUpdateAvailable(false);
removeUpdateDownloadedListener?.();
setIsElectronUpdateAvailable(false);
removeUpdateAvailableListener?.();
});
return () => {
removeUpdateErrorListener?.();
removeUpdateDownloadedListener?.();
removeUpdateAvailableListener?.();
};
}, []);

View File

@ -17,7 +17,6 @@ import {
EDITABLE_INPUT_CSS_SELECTOR,
EDITABLE_INPUT_ID,
GENERAL_TOPIC_ID,
IS_ELECTRON,
MAX_SCREEN_WIDTH_FOR_EXPAND_PINNED_MESSAGES,
MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN,
MOBILE_SCREEN_MAX_WIDTH,
@ -60,7 +59,7 @@ import buildClassName from '../../util/buildClassName';
import buildStyle from '../../util/buildStyle';
import captureEscKeyListener from '../../util/captureEscKeyListener';
import {
IS_ANDROID, IS_IOS, IS_TRANSLATION_SUPPORTED, MASK_IMAGE_DISABLED,
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_TRANSLATION_SUPPORTED, MASK_IMAGE_DISABLED,
} from '../../util/windowEnvironment';
import calculateMiddleFooterTransforms from './helpers/calculateMiddleFooterTransforms';

View File

@ -1,7 +1,7 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo } from '../../../lib/teact/teact';
import { IS_ELECTRON, PRODUCTION_URL } from '../../../config';
import { BASE_URL, IS_ELECTRON_BUILD } from '../../../config';
import buildClassName from '../../../util/buildClassName';
import { handleEmojiLoad, LOADED_EMOJIS } from '../../../util/emoji';
import { IS_EMOJI_SUPPORTED } from '../../../util/windowEnvironment';
@ -31,7 +31,7 @@ const EmojiButton: FC<OwnProps> = ({
focus && 'focus',
);
const src = `${IS_ELECTRON ? PRODUCTION_URL : '.'}/img-apple-64/${emoji.image}.png`;
const src = `${IS_ELECTRON_BUILD ? BASE_URL : '.'}/img-apple-64/${emoji.image}.png`;
const isLoaded = LOADED_EMOJIS.has(src);
return (

View File

@ -29,7 +29,7 @@ import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessag
import { MAIN_THREAD_ID } from '../../../api/types';
import { AudioOrigin } from '../../../types';
import { EMOJI_STATUS_LOOP_LIMIT, GENERAL_TOPIC_ID, IS_ELECTRON } from '../../../config';
import { EMOJI_STATUS_LOOP_LIMIT, GENERAL_TOPIC_ID } from '../../../config';
import {
areReactionsEmpty,
getMessageContent,
@ -95,7 +95,7 @@ import {
import { isAnimatingScroll } from '../../../util/animateScroll';
import buildClassName from '../../../util/buildClassName';
import { isElementInViewport } from '../../../util/isElementInViewport';
import { IS_ANDROID, IS_TRANSLATION_SUPPORTED } from '../../../util/windowEnvironment';
import { IS_ANDROID, IS_ELECTRON, IS_TRANSLATION_SUPPORTED } from '../../../util/windowEnvironment';
import {
calculateDimensionsForMessageMedia,
getStickerDimensions,

View File

@ -252,6 +252,20 @@
}
}
&.green {
background-color: var(--color-green);
color: var(--color-white);
--ripple-color: rgba(0, 0, 0, 0.08);
@include active-styles() {
background-color: var(--color-green-darker);
}
@include no-ripple-styles() {
background-color: var(--color-green);
}
}
&.smaller {
height: 2.75rem;
padding: 0.3125rem;

View File

@ -20,7 +20,7 @@ export type OwnProps = {
size?: 'default' | 'smaller' | 'tiny';
color?: (
'primary' | 'secondary' | 'gray' | 'danger' | 'translucent' | 'translucent-white' | 'translucent-black'
| 'translucent-bordered' | 'dark'
| 'translucent-bordered' | 'dark' | 'green'
);
backgroundImage?: string;
id?: string;

View File

@ -7,17 +7,18 @@ export const RELEASE_DATETIME = process.env.RELEASE_DATETIME;
export const PRODUCTION_HOSTNAME = 'web.telegram.org';
export const PRODUCTION_URL = 'https://web.telegram.org/a';
export const WEB_VERSION_BASE = 'https://web.telegram.org/'; // Used to redirect to other versions
export const BASE_URL = process.env.BASE_URL;
export const IS_MOCKED_CLIENT = process.env.APP_MOCKED_CLIENT === '1';
export const IS_TEST = process.env.APP_ENV === 'test';
export const IS_PERF = process.env.APP_ENV === 'perf';
export const IS_BETA = process.env.APP_ENV === 'staging';
export const IS_ELECTRON = process.env.IS_ELECTRON;
export const IS_ELECTRON_BUILD = process.env.IS_ELECTRON_BUILD;
export const DEBUG = process.env.APP_ENV !== 'production';
export const DEBUG_MORE = false;
export const DEBUG_LOG_FILENAME = 'tt-log.json';
export const STRICTERDOM_ENABLED = DEBUG && !IS_ELECTRON;
export const STRICTERDOM_ENABLED = DEBUG;
export const BETA_CHANGELOG_URL = 'https://telegra.ph/WebA-Beta-03-20';
export const ELECTRON_HOST_URL = process.env.ELECTRON_HOST_URL!;

View File

@ -1,43 +1,106 @@
import type { BrowserWindow } from 'electron';
import { ipcMain } from 'electron';
import {
app, BrowserWindow, ipcMain, net,
} from 'electron';
import type { UpdateInfo } from 'electron-updater';
import { autoUpdater } from 'electron-updater';
import type { WindowState } from './windowState';
import { ElectronAction, ElectronEvent } from '../types/electron';
import { forceQuit, IS_MAC_OS, windows } from './utils';
import { PRODUCTION_URL } from '../config';
import getIsAppUpdateNeeded from '../util/getIsAppUpdateNeeded';
import { pause } from '../util/schedulers';
import {
forceQuit, IS_MAC_OS, IS_PREVIEW, IS_WINDOWS, store,
} from './utils';
let interval: NodeJS.Timer;
export const AUTO_UPDATE_SETTING_KEY = 'autoUpdate';
const ELECTRON_APP_VERSION_URL = 'electronVersion.txt';
const CHECK_UPDATE_INTERVAL = 5 * 60 * 1000;
export default function setupAutoUpdates(window: BrowserWindow, state: WindowState) {
if (!interval) {
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.checkForUpdates();
let isUpdateCheckStarted = false;
interval = setInterval(() => autoUpdater.checkForUpdates(), CHECK_UPDATE_INTERVAL);
ipcMain.handle(ElectronAction.INSTALL_UPDATE, () => {
state.saveLastUrlHash();
if (IS_MAC_OS) {
forceQuit.enable();
}
return autoUpdater.quitAndInstall();
});
export default function setupAutoUpdates(state: WindowState) {
if (isUpdateCheckStarted) {
return;
}
autoUpdater.on('error', (error: Error) => {
if (windows.has(window)) {
window.webContents.send(ElectronEvent.UPDATE_ERROR, error);
isUpdateCheckStarted = true;
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = true;
checkForUpdates();
ipcMain.handle(ElectronAction.INSTALL_UPDATE, () => {
state.saveLastUrlHash();
if (IS_MAC_OS || IS_WINDOWS) {
forceQuit.enable();
}
return autoUpdater.quitAndInstall();
});
autoUpdater.on('error', (error: Error) => {
BrowserWindow.getAllWindows().forEach((window) => {
window.webContents.send(ElectronEvent.UPDATE_ERROR, error);
});
});
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
if (windows.has(window)) {
window.webContents.send(ElectronEvent.UPDATE_DOWNLOADED, info);
}
BrowserWindow.getAllWindows().forEach((window) => {
window.webContents.send(ElectronEvent.UPDATE_AVAILABLE, info);
});
});
}
export function getIsAutoUpdateEnabled() {
return !IS_PREVIEW && store.get(AUTO_UPDATE_SETTING_KEY);
}
async function checkForUpdates(): Promise<void> {
while (true) { // eslint-disable-line no-constant-condition
if (await shouldPerformAutoUpdate()) {
if (getIsAutoUpdateEnabled()) {
autoUpdater.checkForUpdates();
return;
}
BrowserWindow.getAllWindows().forEach((window) => {
window.webContents.send(ElectronEvent.UPDATE_AVAILABLE);
});
}
await pause(CHECK_UPDATE_INTERVAL);
}
}
function shouldPerformAutoUpdate(): Promise<boolean> {
return new Promise((resolve) => {
const request = net.request(`${PRODUCTION_URL}/${ELECTRON_APP_VERSION_URL}?${Date.now()}`);
request.on('response', (response) => {
let contents = '';
response.on('end', () => {
resolve(getIsAppUpdateNeeded(contents, app.getVersion()));
});
response.on('data', (data: Buffer) => {
contents = `${contents}${String(data)}`;
});
response.on('error', () => {
resolve(false);
});
});
request.on('error', () => {
resolve(false);
});
request.end();
});
}

View File

@ -0,0 +1,21 @@
import { getLastWindow } from './utils';
let localStorage: Record<string, any> | undefined;
export async function captureLocalStorage(): Promise<void> {
localStorage = await (getLastWindow())?.webContents.executeJavaScript('({ ...localStorage });');
}
export async function restoreLocalStorage(): Promise<void> {
if (!localStorage) {
return;
}
await getLastWindow()?.webContents.executeJavaScript(
Object.keys(localStorage).map(
(key: string) => `localStorage.setItem('${key}', JSON.stringify(${localStorage![key]}))`,
).join(';'),
);
localStorage = undefined;
}

View File

@ -12,8 +12,11 @@ const electronApi: ElectronApi = {
setWindowTitle: (title?: string) => ipcRenderer.invoke(ElectronAction.SET_WINDOW_TITLE, title),
setTrafficLightPosition:
(position: TrafficLightPosition) => ipcRenderer.invoke(ElectronAction.SET_TRAFFIC_LIGHT_POSITION, position),
setIsAutoUpdateEnabled: (value: boolean) => ipcRenderer.invoke(ElectronAction.SET_IS_AUTO_UPDATE_ENABLED, value),
getIsAutoUpdateEnabled: () => ipcRenderer.invoke(ElectronAction.GET_IS_AUTO_UPDATE_ENABLED),
setIsTrayIconEnabled: (value: boolean) => ipcRenderer.invoke(ElectronAction.SET_IS_TRAY_ICON_ENABLED, value),
getIsTrayIconEnabled: () => ipcRenderer.invoke(ElectronAction.GET_IS_TRAY_ICON_ENABLED),
restoreLocalStorage: () => ipcRenderer.invoke(ElectronAction.RESTORE_LOCAL_STORAGE),
on: (eventName: ElectronEvent, callback) => {
const subscription = (event: IpcRendererEvent, ...args: any) => callback(...args);

View File

@ -1,12 +1,15 @@
import type { Point } from 'electron';
import { app, BrowserWindow } from 'electron';
import Store from 'electron-store';
import fs from 'fs';
import type { TrafficLightPosition } from '../types/electron';
export const IS_MAC_OS = process.platform === 'darwin';
export const IS_WINDOWS = process.platform === 'win32';
export const IS_LINUX = process.platform === 'linux';
export const IS_PREVIEW = process.env.IS_PREVIEW === 'true';
export const IS_FIRST_RUN = !fs.existsSync(`${app.getPath('userData')}/config.json`);
export const windows = new Set<BrowserWindow>();
export const store: Store = new Store();
@ -23,6 +26,18 @@ export function hasExtraWindows(): boolean {
return BrowserWindow.getAllWindows().length > 1;
}
export function reloadWindows(isAutoUpdateEnabled = true): void {
BrowserWindow.getAllWindows().forEach((window: BrowserWindow) => {
const { hash } = new URL(window.webContents.getURL());
if (isAutoUpdateEnabled) {
window.loadURL(`${process.env.BASE_URL}${hash}`);
} else {
window.loadURL(`file://${__dirname}/index.html${hash}`);
}
});
}
export function getAppTitle(chatTitle?: string): string {
const appName = app.getName();

View File

@ -7,12 +7,13 @@ import path from 'path';
import type { TrafficLightPosition } from '../types/electron';
import { ElectronAction, ElectronEvent } from '../types/electron';
import setupAutoUpdates from './autoUpdates';
import setupAutoUpdates, { AUTO_UPDATE_SETTING_KEY, getIsAutoUpdateEnabled } from './autoUpdates';
import { processDeeplink } from './deeplink';
import { captureLocalStorage, restoreLocalStorage } from './localStorage';
import tray from './tray';
import {
forceQuit, getAppTitle, getCurrentWindow, getLastWindow, hasExtraWindows, IS_MAC_OS, IS_WINDOWS,
TRAFFIC_LIGHT_POSITION, windows,
forceQuit, getAppTitle, getCurrentWindow, getLastWindow, hasExtraWindows, IS_FIRST_RUN, IS_MAC_OS,
IS_PREVIEW, IS_WINDOWS, reloadWindows, store, TRAFFIC_LIGHT_POSITION, windows,
} from './utils';
import windowStateKeeper from './windowState';
@ -68,10 +69,6 @@ export function createWindow(url?: string) {
}),
});
window.on('page-title-updated', (event: Event) => {
event.preventDefault();
});
windowState.manage(window);
window.webContents.setWindowOpenHandler((details: HandlerDetails) => {
@ -83,6 +80,10 @@ export function createWindow(url?: string) {
return deviceType === 'hid' && ALLOWED_DEVICE_ORIGINS.includes(origin);
});
window.on('page-title-updated', (event: Event) => {
event.preventDefault();
});
window.on('enter-full-screen', () => {
window.webContents.send(ElectronEvent.FULLSCREEN_CHANGE, true);
});
@ -106,15 +107,6 @@ export function createWindow(url?: string) {
}
});
if (url) {
window.loadURL(url);
} else if (app.isPackaged) {
window.loadURL(`file://${__dirname}/index.html${windowState.urlHash}`);
} else {
window.loadURL(`http://localhost:1234${windowState.urlHash}`);
window.webContents.openDevTools();
}
windowState.clearLastUrlHash();
if (!IS_MAC_OS) {
@ -126,16 +118,40 @@ export function createWindow(url?: string) {
tray.create();
}
window.webContents.once('dom-ready', () => {
window.show();
window.webContents.once('dom-ready', async () => {
processDeeplink();
if (process.env.APP_ENV === 'production') {
setupAutoUpdates(window, windowState);
setupAutoUpdates(windowState);
}
if (!IS_FIRST_RUN && getIsAutoUpdateEnabled() === undefined) {
store.set(AUTO_UPDATE_SETTING_KEY, true);
await captureLocalStorage();
reloadWindows();
}
window.show();
});
windows.add(window);
loadWindowUrl(window, url, windowState.urlHash);
}
function loadWindowUrl(window: BrowserWindow, url?: string, hash?: string): void {
if (url) {
window.loadURL(url);
} else if (!app.isPackaged) {
window.loadURL(`http://localhost:1234${hash}`);
window.webContents.openDevTools();
} else if (getIsAutoUpdateEnabled()) {
window.loadURL(`${process.env.BASE_URL}${hash}`);
} else if (getIsAutoUpdateEnabled() === undefined && IS_FIRST_RUN) {
store.set(AUTO_UPDATE_SETTING_KEY, true);
window.loadURL(`${process.env.BASE_URL}${hash}`);
} else {
window.loadURL(`file://${__dirname}/index.html${hash}`);
}
}
export function setupElectronActionHandlers() {
@ -174,8 +190,22 @@ export function setupElectronActionHandlers() {
getCurrentWindow()?.setTrafficLightPosition(TRAFFIC_LIGHT_POSITION[position]);
});
ipcMain.handle(ElectronAction.SET_IS_TRAY_ICON_ENABLED, (_, value: boolean) => {
if (value) {
ipcMain.handle(ElectronAction.SET_IS_AUTO_UPDATE_ENABLED, async (_, isAutoUpdateEnabled: boolean) => {
if (IS_PREVIEW) {
return;
}
store.set(AUTO_UPDATE_SETTING_KEY, isAutoUpdateEnabled);
await captureLocalStorage();
reloadWindows(isAutoUpdateEnabled);
});
ipcMain.handle(ElectronAction.GET_IS_AUTO_UPDATE_ENABLED, () => {
return getIsAutoUpdateEnabled();
});
ipcMain.handle(ElectronAction.SET_IS_TRAY_ICON_ENABLED, (_, isTrayIconEnabled: boolean) => {
if (isTrayIconEnabled) {
tray.enable();
} else {
tray.disable();
@ -183,6 +213,8 @@ export function setupElectronActionHandlers() {
});
ipcMain.handle(ElectronAction.GET_IS_TRAY_ICON_ENABLED, () => tray.isEnabled);
ipcMain.handle(ElectronAction.RESTORE_LOCAL_STORAGE, () => restoreLocalStorage());
}
export function setupCloseHandlers() {

View File

@ -1,9 +1,9 @@
import type { ActionReturnType } from '../../types';
import { MAIN_THREAD_ID } from '../../../api/types';
import { IS_ELECTRON } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { createMessageHashUrl } from '../../../util/routing';
import { IS_ELECTRON } from '../../../util/windowEnvironment';
import { addActionHandler, setGlobal } from '../../index';
import {
exitMessageSelectMode, replaceTabThreadParam, updateCurrentMessageList, updateRequestedChatTranslation,

View File

@ -2,7 +2,6 @@ import { addCallback } from '../../../lib/teact/teactn';
import type { ActionReturnType, GlobalState } from '../../types';
import { IS_ELECTRON } from '../../../config';
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
import { setLanguage } from '../../../util/langProvider';
@ -14,7 +13,7 @@ import switchTheme from '../../../util/switchTheme';
import { getSystemTheme, setSystemThemeChangeCallback } from '../../../util/systemTheme';
import { startWebsync, stopWebsync } from '../../../util/websync';
import {
IS_ANDROID, IS_IOS, IS_LINUX,
IS_ANDROID, IS_ELECTRON, IS_IOS, IS_LINUX,
IS_MAC_OS, IS_SAFARI, IS_TOUCH_ENV, IS_WINDOWS,
} from '../../../util/windowEnvironment';
import { callApi } from '../../../api/gramjs';

View File

@ -5,17 +5,19 @@ import type { ActionReturnType, GlobalState } from '../../types';
import { MAIN_THREAD_ID } from '../../../api/types';
import {
DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT, INACTIVE_MARKER, IS_ELECTRON,
DEBUG, GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT, INACTIVE_MARKER,
PAGE_TITLE,
} from '../../../config';
import { getAllMultitabTokens, getCurrentTabId, reestablishMasterToSelf } from '../../../util/establishMultitabRole';
import { getAllNotificationsCount } from '../../../util/folderManager';
import generateUniqueId from '../../../util/generateUniqueId';
import getIsAppUpdateNeeded from '../../../util/getIsAppUpdateNeeded';
import getReadableErrorText from '../../../util/getReadableErrorText';
import { compact, unique } from '../../../util/iteratees';
import * as langProvider from '../../../util/langProvider';
import updateIcon from '../../../util/updateIcon';
import { setPageTitle, setPageTitleInstant } from '../../../util/updatePageTitle';
import { IS_ELECTRON } from '../../../util/windowEnvironment';
import { getAllowedAttachmentOptions, getChatTitle } from '../../helpers';
import {
addActionHandler, getActions, getGlobal, setGlobal,
@ -612,22 +614,16 @@ addActionHandler('closeMapModal', (global, actions, payload): ActionReturnType =
});
addActionHandler('checkAppVersion', (global): ActionReturnType => {
if (IS_ELECTRON) {
return;
}
const APP_VERSION_REGEX = /^\d+\.\d+(\.\d+)?$/;
fetch(`${APP_VERSION_URL}?${Date.now()}`)
.then((response) => response.text())
.then((version) => {
version = version.trim();
if (APP_VERSION_REGEX.test(version) && version !== APP_VERSION) {
if (getIsAppUpdateNeeded(version, APP_VERSION)) {
global = getGlobal();
global = {
...global,
isUpdateAvailable: true,
isAppUpdateAvailable: true,
};
setGlobal(global);
}
@ -640,11 +636,11 @@ addActionHandler('checkAppVersion', (global): ActionReturnType => {
});
});
addActionHandler('setIsAppUpdateAvailable', (global, action, payload): ActionReturnType => {
addActionHandler('setIsElectronUpdateAvailable', (global, action, payload): ActionReturnType => {
global = getGlobal();
global = {
...global,
isUpdateAvailable: Boolean(payload),
isElectronUpdateAvailable: Boolean(payload),
};
setGlobal(global);
});

View File

@ -66,7 +66,8 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
attachMenu: { bots: {} },
passcode: {},
twoFaSettings: {},
isUpdateAvailable: false,
isAppUpdateAvailable: false,
isElectronUpdateAvailable: false,
shouldShowContextMenuHint: true,
audioPlayer: {

View File

@ -628,7 +628,8 @@ export type GlobalState = {
connectionState?: ApiUpdateConnectionStateType;
currentUserId?: string;
isSyncing?: boolean;
isUpdateAvailable?: boolean;
isAppUpdateAvailable?: boolean;
isElectronUpdateAvailable?: boolean;
isSynced?: boolean;
isFetchingDifference?: boolean;
leftColumnWidth?: number;
@ -1660,7 +1661,7 @@ export interface ActionPayloads {
openLimitReachedModal: { limit: ApiLimitTypeWithModal } & WithTabId;
closeLimitReachedModal: WithTabId | undefined;
checkAppVersion: undefined;
setIsAppUpdateAvailable: boolean;
setIsElectronUpdateAvailable: boolean;
setGlobalSearchClosing: ({
isClosing?: boolean;
} & WithTabId) | undefined;

View File

@ -4,13 +4,13 @@ import { getActions } from '../global';
import type { ApiChat, ApiUser } from '../api/types';
import type { MenuItemContextAction } from '../components/ui/ListItem';
import { IS_ELECTRON, SERVICE_NOTIFICATIONS_USER_ID } from '../config';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../config';
import {
getCanDeleteChat, isChatArchived, isChatChannel, isChatGroup,
isUserId,
} from '../global/helpers';
import { compact } from '../util/iteratees';
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../util/windowEnvironment';
import { IS_ELECTRON, IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../util/windowEnvironment';
import useLang from './useLang';
const useChatContextActions = ({

View File

@ -1,12 +1,14 @@
import { useEffect, useState } from '../lib/teact/teact';
import { getActions } from '../global';
import type { ThemeKey } from '../types';
import { CUSTOM_BG_CACHE_NAME } from '../config';
import { CUSTOM_BG_CACHE_NAME, DARK_THEME_PATTERN_COLOR, DEFAULT_PATTERN_COLOR } from '../config';
import * as cacheApi from '../util/cacheApi';
import { preloadImage } from '../util/files';
const useCustomBackground = (theme: ThemeKey, settingValue?: string) => {
const { setThemeSettings } = getActions();
const [value, setValue] = useState(settingValue);
useEffect(() => {
@ -24,6 +26,15 @@ const useCustomBackground = (theme: ThemeKey, settingValue?: string) => {
.then(() => {
setValue(`url(${url})`);
});
})
.catch(() => {
setThemeSettings({
theme,
background: undefined,
backgroundColor: undefined,
isBlurred: true,
patternColor: theme === 'dark' ? DARK_THEME_PATTERN_COLOR : DEFAULT_PATTERN_COLOR,
});
});
}
}, [settingValue, theme]);

View File

@ -1,8 +1,7 @@
import type { RefObject } from 'react';
import { useEffect, useRef } from '../lib/teact/teact';
import { IS_ELECTRON } from '../config';
import { IS_MAC_OS } from '../util/windowEnvironment';
import { IS_ELECTRON, IS_MAC_OS } from '../util/windowEnvironment';
const DRAG_DISTANCE_THRESHOLD = 5;

View File

@ -40,6 +40,8 @@ async function init() {
checkAndAssignPermanentWebVersion();
await window.electron?.restoreLocalStorage();
if (IS_MULTITAB_SUPPORTED) {
subscribeToMultitabBroadcastChannel();

View File

@ -1,4 +1,4 @@
import { DEBUG, ELECTRON_HOST_URL, IS_ELECTRON } from '../config';
import { DEBUG, ELECTRON_HOST_URL, IS_ELECTRON_BUILD } from '../config';
import { pause } from '../util/schedulers';
import { clearAssetCache, respondWithCache, respondWithCacheNetworkFirst } from './assetCache';
import { respondForDownload } from './download';
@ -47,7 +47,7 @@ self.addEventListener('activate', (e) => {
self.addEventListener('fetch', (e: FetchEvent) => {
const { url } = e.request;
const scope = IS_ELECTRON ? ELECTRON_HOST_URL : self.registration.scope;
const scope = IS_ELECTRON_BUILD ? ELECTRON_HOST_URL : self.registration.scope;
if (!url.startsWith(scope)) {
return false;
}

View File

@ -31,6 +31,7 @@
"--color-default-shadow": ["#72727240", "#1010109c"],
"--color-light-shadow": ["#7272722B", "#00000040"],
"--color-green": ["#00C73E", "#8774E1"],
"--color-green-darker": ["#00a734", "#7b71c6"],
"--color-text-meta-colored": ["#4DCD5E", "#8378DB"],
"--color-reply-hover": ["#F4F4F4", "#272727"],
"--color-reply-active": ["#E8E9E9", "#2E2F2F"],

View File

@ -1,7 +1,7 @@
export enum ElectronEvent {
FULLSCREEN_CHANGE = 'fullscreen-change',
UPDATE_ERROR = 'update-error',
UPDATE_DOWNLOADED = 'update-downloaded',
UPDATE_AVAILABLE = 'update-available',
DEEPLINK = 'deeplink',
}
@ -12,8 +12,11 @@ export enum ElectronAction {
OPEN_NEW_WINDOW = 'open-new-window',
SET_WINDOW_TITLE = 'set-window-title',
SET_TRAFFIC_LIGHT_POSITION = 'set-traffic-light-position',
SET_IS_AUTO_UPDATE_ENABLED = 'set-is-auto-update-enabled',
GET_IS_AUTO_UPDATE_ENABLED = 'get-is-auto-update-enabled',
SET_IS_TRAY_ICON_ENABLED = 'set-is-tray-icon-enabled',
GET_IS_TRAY_ICON_ENABLED = 'get-is-tray-icon-enabled',
RESTORE_LOCAL_STORAGE = 'restore-local-storage',
}
export type TrafficLightPosition = 'standard' | 'lowered';
@ -25,8 +28,11 @@ export interface ElectronApi {
openNewWindow: (url: string, title?: string) => Promise<void>;
setWindowTitle: (title?: string) => Promise<void>;
setTrafficLightPosition: (position: TrafficLightPosition) => Promise<void>;
setIsAutoUpdateEnabled: (value: boolean) => Promise<void>;
getIsAutoUpdateEnabled: () => Promise<boolean>;
setIsTrayIconEnabled: (value: boolean) => Promise<void>;
getIsTrayIconEnabled: () => Promise<boolean>;
restoreLocalStorage: () => Promise<void>;
on: (eventName: ElectronEvent, callback: any) => VoidFunction;
}

View File

@ -1,4 +1,4 @@
import { ELECTRON_HOST_URL, IS_ELECTRON } from '../config';
import { ELECTRON_HOST_URL, IS_ELECTRON_BUILD } from '../config';
// eslint-disable-next-line no-restricted-globals
const cacheApi = self.caches;
@ -28,7 +28,7 @@ export async function fetch(
try {
// To avoid the error "Request scheme 'webdocument' is unsupported"
const request = IS_ELECTRON
const request = IS_ELECTRON_BUILD
? `${ELECTRON_HOST_URL}/${key.replace(/:/g, '_')}`
: new Request(key.replace(/:/g, '_'));
const cache = await cacheApi.open(cacheName);
@ -88,7 +88,7 @@ export async function save(cacheName: string, key: string, data: AnyLiteral | Bl
? data
: JSON.stringify(data);
// To avoid the error "Request scheme 'webdocument' is unsupported"
const request = IS_ELECTRON
const request = IS_ELECTRON_BUILD
? `${ELECTRON_HOST_URL}/${key.replace(/:/g, '_')}`
: new Request(key.replace(/:/g, '_'));
const response = new Response(cacheData);

View File

@ -0,0 +1,5 @@
const APP_VERSION_REGEX = /^\d+\.\d+(\.\d+)?$/;
export default function getIsAppUpdateNeeded(remoteVersion: string, appVersion: string) {
return APP_VERSION_REGEX.test(remoteVersion) && remoteVersion !== appVersion;
}

View File

@ -9,7 +9,7 @@ import {
import {
DEBUG, ELECTRON_HOST_URL,
IS_ELECTRON, MEDIA_CACHE_DISABLED, MEDIA_CACHE_NAME, MEDIA_CACHE_NAME_AVATARS,
IS_ELECTRON_BUILD, MEDIA_CACHE_DISABLED, MEDIA_CACHE_NAME, MEDIA_CACHE_NAME_AVATARS,
} from '../config';
import { callApi, cancelApiProgress } from '../api/gramjs';
import * as cacheApi from './cacheApi';
@ -26,7 +26,7 @@ const asCacheApiType = {
[ApiMediaFormat.Progressive]: undefined,
};
const PROGRESSIVE_URL_PREFIX = `${IS_ELECTRON ? ELECTRON_HOST_URL : '.'}/progressive/`;
const PROGRESSIVE_URL_PREFIX = `${IS_ELECTRON_BUILD ? ELECTRON_HOST_URL : '.'}/progressive/`;
const URL_DOWNLOAD_PREFIX = './download/';
const RETRY_MEDIA_AFTER = 2000;
const MAX_MEDIA_RETRIES = 3;

View File

@ -6,9 +6,7 @@ import type {
} from '../api/types';
import { ApiMediaFormat } from '../api/types';
import {
APP_NAME, DEBUG, IS_ELECTRON, IS_TEST,
} from '../config';
import { APP_NAME, DEBUG, IS_TEST } from '../config';
import {
getChatAvatarHash,
getChatTitle,
@ -39,7 +37,7 @@ import { buildCollectionByKey } from './iteratees';
import { translate } from './langProvider';
import * as mediaLoader from './mediaLoader';
import { debounce } from './schedulers';
import { IS_SERVICE_WORKER_SUPPORTED, IS_TOUCH_ENV } from './windowEnvironment';
import { IS_ELECTRON, IS_SERVICE_WORKER_SUPPORTED, IS_TOUCH_ENV } from './windowEnvironment';
function getDeviceToken(subscription: PushSubscription) {
const data = subscription.toJSON();

View File

@ -1,5 +1,5 @@
import { IS_ELECTRON } from '../config';
import { debounce } from './schedulers';
import { IS_ELECTRON } from './windowEnvironment';
const UPDATE_DEBOUNCE_MS = 200;

View File

@ -2,9 +2,10 @@ import { getGlobal } from '../global';
import {
APP_CODE_NAME,
DEBUG, IS_ELECTRON, IS_MOCKED_CLIENT,
DEBUG, IS_MOCKED_CLIENT,
} from '../config';
import { hasStoredSession } from './sessions';
import { IS_ELECTRON } from './windowEnvironment';
const WEBSYNC_URLS = [
't.me',

View File

@ -1,8 +1,4 @@
import {
IS_ELECTRON,
IS_TEST,
PRODUCTION_HOSTNAME,
} from '../config';
import { IS_TEST, PRODUCTION_HOSTNAME } from '../config';
export function getPlatform() {
const { userAgent, platform } = window.navigator;
@ -40,6 +36,7 @@ export const IS_YA_BROWSER = navigator.userAgent.includes('YaBrowser');
export const IS_FIREFOX = navigator.userAgent.toLowerCase().includes('firefox')
|| navigator.userAgent.toLowerCase().includes('iceweasel')
|| navigator.userAgent.toLowerCase().includes('icecat');
export const IS_ELECTRON = Boolean(window.electron);
export enum MouseButton {
Main = 0,

View File

@ -1,7 +1,15 @@
import path from 'path';
import { EnvironmentPlugin } from 'webpack';
const { APP_ENV = 'production' } = process.env;
import { PRODUCTION_URL } from './src/config';
// GitHub workflow uses an empty string as the default value if it's not in repository variables, so we cannot define a default value here
process.env.BASE_URL = process.env.BASE_URL || PRODUCTION_URL;
const {
APP_ENV = 'production',
BASE_URL,
} = process.env;
export default {
mode: 'production',
@ -23,7 +31,11 @@ export default {
},
plugins: [
new EnvironmentPlugin({ APP_ENV }),
new EnvironmentPlugin({
APP_ENV,
BASE_URL,
IS_PREVIEW: false,
}),
],
module: {

View File

@ -16,21 +16,25 @@ import {
ProvidePlugin,
} from 'webpack';
import { PRODUCTION_URL } from './src/config';
import { version as appVersion } from './package.json';
const {
HEAD,
APP_ENV = 'production',
APP_MOCKED_CLIENT = '',
IS_ELECTRON,
IS_ELECTRON_BUILD,
} = process.env;
dotenv.config();
const DEFAULT_APP_TITLE = `Telegram${APP_ENV !== 'production' ? ' Beta' : ''}`;
// GitHub workflow uses an empty string as the default value if it's not in repository variables, so we cannot define a default value here
process.env.BASE_URL = process.env.BASE_URL || PRODUCTION_URL;
const {
BASE_URL = 'https://web.telegram.org/a/',
BASE_URL,
ELECTRON_HOST_URL = 'https://telegram-a-host',
APP_TITLE = DEFAULT_APP_TITLE,
} = process.env;
@ -40,8 +44,9 @@ const CSP = `
connect-src 'self' wss://*.web.telegram.org blob: http: https: ${APP_ENV === 'development' ? 'wss:' : ''};
script-src 'self' 'wasm-unsafe-eval' https://t.me/_websync_ https://telegram.me/_websync_;
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https://ss3.4sqi.net/img/categories_v2/ ${IS_ELECTRON ? BASE_URL : ''};
media-src 'self' blob: data: ${IS_ELECTRON ? [BASE_URL, ELECTRON_HOST_URL].join(' ') : ''};
img-src 'self' data: blob: https://ss3.4sqi.net/img/categories_v2/
${IS_ELECTRON_BUILD ? `${BASE_URL}/` : ''};
media-src 'self' blob: data: ${IS_ELECTRON_BUILD ? [`${BASE_URL}/`, ELECTRON_HOST_URL].join(' ') : ''};
object-src 'none';
frame-src http: https:;
base-uri 'none';
@ -211,14 +216,15 @@ export default function createConfig(
APP_MOCKED_CLIENT,
// eslint-disable-next-line no-null/no-null
APP_NAME: null,
IS_ELECTRON: false,
APP_TITLE,
RELEASE_DATETIME: Date.now(),
TELEGRAM_API_ID: undefined,
TELEGRAM_API_HASH: undefined,
// eslint-disable-next-line no-null/no-null
TEST_SESSION: null,
IS_ELECTRON_BUILD: false,
ELECTRON_HOST_URL,
BASE_URL,
}),
// Updates each dev re-build to provide current git branch or commit hash
new DefinePlugin({
@ -247,7 +253,7 @@ export default function createConfig(
}),
],
devtool: APP_ENV === 'production' && IS_ELECTRON ? undefined : 'source-map',
devtool: APP_ENV === 'production' && IS_ELECTRON_BUILD ? undefined : 'source-map',
optimization: {
splitChunks: {