diff --git a/apps/readest-app/src/components/AboutWindow.tsx b/apps/readest-app/src/components/AboutWindow.tsx
index 019c23f2..d350712d 100644
--- a/apps/readest-app/src/components/AboutWindow.tsx
+++ b/apps/readest-app/src/components/AboutWindow.tsx
@@ -1,9 +1,11 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import Image from 'next/image';
import packageJson from '../../package.json';
+import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
import { hasUpdater } from '@/services/environment';
import { checkForAppUpdates } from '@/helpers/updater';
+import { parseWebViewVersion } from '@/utils/ua';
import Dialog from './Dialog';
export const setAboutDialogVisible = (visible: boolean) => {
@@ -17,7 +19,14 @@ export const setAboutDialogVisible = (visible: boolean) => {
export const AboutWindow = () => {
const _ = useTranslation();
- const [isUpdated, setIsUpdated] = React.useState(false);
+ const { appService } = useEnv();
+ const [isUpdated, setIsUpdated] = useState(false);
+ const [browserInfo, setBrowserInfo] = useState('');
+
+ useEffect(() => {
+ setBrowserInfo(parseWebViewVersion(appService));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
const handleCheckUpdate = async () => {
const update = await checkForAppUpdates(_, false);
@@ -37,13 +46,15 @@ export const AboutWindow = () => {
>
-
+
-
Readest
-
- {_('Version {{version}}', { version: packageJson.version })}
-
+
+
Readest
+
+ {_('Version {{version}}', { version: packageJson.version })} {`(${browserInfo})`}
+
+
{hasUpdater() && !isUpdated && (
{_('Check Update')}
diff --git a/apps/readest-app/src/services/appService.ts b/apps/readest-app/src/services/appService.ts
index 784cbe2f..d963bec7 100644
--- a/apps/readest-app/src/services/appService.ts
+++ b/apps/readest-app/src/services/appService.ts
@@ -1,4 +1,4 @@
-import { AppPlatform, AppService } from '@/types/system';
+import { AppPlatform, AppService, OsPlatform } from '@/types/system';
import { SystemSettings } from '@/types/settings';
import { FileSystem, BaseDir } from '@/types/system';
@@ -41,7 +41,7 @@ import { TxtToEpubConverter } from '@/utils/txt';
import { BOOK_FILE_NOT_FOUND_ERROR } from './errors';
export abstract class BaseAppService implements AppService {
- osPlatform: string = getOSPlatform();
+ osPlatform: OsPlatform = getOSPlatform();
appPlatform: AppPlatform = 'tauri';
localBooksDir = '';
isMobile = false;
diff --git a/apps/readest-app/src/types/system.ts b/apps/readest-app/src/types/system.ts
index 603f3e87..feeb76e1 100644
--- a/apps/readest-app/src/types/system.ts
+++ b/apps/readest-app/src/types/system.ts
@@ -4,6 +4,7 @@ import { BookDoc } from '@/libs/document';
import { ProgressHandler } from '@/utils/transfer';
export type AppPlatform = 'web' | 'tauri';
+export type OsPlatform = 'android' | 'ios' | 'macos' | 'windows' | 'linux' | 'unknown';
export type BaseDir = 'Books' | 'Settings' | 'Data' | 'Log' | 'Cache' | 'None';
export interface FileSystem {
@@ -23,7 +24,7 @@ export interface FileSystem {
export interface AppService {
fs: FileSystem;
- osPlatform: string;
+ osPlatform: OsPlatform;
appPlatform: AppPlatform;
hasTrafficLight: boolean;
hasWindow: boolean;
diff --git a/apps/readest-app/src/utils/misc.ts b/apps/readest-app/src/utils/misc.ts
index 4e2394be..109926be 100644
--- a/apps/readest-app/src/utils/misc.ts
+++ b/apps/readest-app/src/utils/misc.ts
@@ -1,3 +1,4 @@
+import { OsPlatform } from '@/types/system';
import { md5 } from 'js-md5';
export const uniqueId = () => Math.random().toString(36).substring(2, 9);
@@ -56,7 +57,7 @@ export const getUserLocale = (lang: string): string | undefined => {
// Note that iPad may have a user agent string like a desktop browser
// when possible please use appService.isIOSApp || getOSPlatform() === 'ios'
// to check if the app is running on iOS
-export const getOSPlatform = () => {
+export const getOSPlatform = (): OsPlatform => {
const userAgent = navigator.userAgent.toLowerCase();
if (/iphone|ipad|ipod/.test(userAgent)) return 'ios';
@@ -65,7 +66,7 @@ export const getOSPlatform = () => {
if (userAgent.includes('windows nt')) return 'windows';
if (userAgent.includes('linux')) return 'linux';
- return '';
+ return 'unknown';
};
export const isContentURI = (uri: string) => {
diff --git a/apps/readest-app/src/utils/ua.ts b/apps/readest-app/src/utils/ua.ts
new file mode 100644
index 00000000..07b4b399
--- /dev/null
+++ b/apps/readest-app/src/utils/ua.ts
@@ -0,0 +1,65 @@
+import { AppService } from '@/types/system';
+
+export const parseWebViewVersion = (appService: AppService | null): string => {
+ const ua = navigator.userAgent;
+
+ if (appService?.isAndroidApp) {
+ // Android WebView
+ const chromeMatch = ua.match(/Chrome\/([0-9.]+)/);
+ return chromeMatch ? `WebView ${chromeMatch[1]}` : 'Android WebView';
+ } else if (appService?.isIOSApp) {
+ // iOS WebView
+ const webkitMatch = ua.match(/AppleWebKit\/([0-9.]+)/);
+ return webkitMatch ? `WebView ${webkitMatch[1]}` : 'iOS WebView';
+ } else if (appService?.isMacOSApp) {
+ // macOS WebView
+ const webkitMatch = ua.match(/AppleWebKit\/([0-9.]+)/);
+ return webkitMatch ? `WebView ${webkitMatch[1]}` : 'macOS WebView';
+ } else if (appService?.appPlatform === 'tauri' && appService?.osPlatform === 'windows') {
+ // Windows WebView2
+ const match = ua.match(/Edg\/([0-9.]+)/);
+ return match ? `Edge ${match[1]}` : 'Edge WebView2';
+ } else if (appService?.appPlatform === 'tauri' && appService?.osPlatform === 'linux') {
+ // Linux WebView
+ const match = ua.match(/Chromium\/([0-9.]+)/);
+ return match ? `WebView ${match[1]}` : 'Linux WebView';
+ } else if (ua.includes('CriOS') && ua.includes('Mobile/') && ua.includes('Safari')) {
+ // iOS Chrome WebView
+ const match = ua.match(/CriOS\/([0-9.]+)/);
+ return match ? `Chrome ${match[1]}` : 'iOS Chrome';
+ } else if (ua.includes('FxiOS') && ua.includes('Mobile/') && ua.includes('Safari')) {
+ // iOS Firefox WebView
+ const match = ua.match(/FxiOS\/([0-9.]+)/);
+ return match ? `Firefox ${match[1]}` : 'iOS Firefox';
+ } else if (ua.includes('Chrome') && ua.includes('AppleWebKit') && ua.includes('Macintosh')) {
+ // macOS Chrome
+ const match = ua.match(/Chrome\/([0-9.]+)/);
+ return match ? `Chrome ${match[1]}` : 'macOS Chrome';
+ } else if (ua.includes('Safari') && ua.includes('AppleWebKit') && ua.includes('Macintosh')) {
+ // macOS Safari
+ const match = ua.match(/Safari\/([0-9.]+)/);
+ return match ? `Safari ${match[1]}` : 'macOS Safari';
+ } else if (ua.includes('Edg/')) {
+ // Microsoft Edge
+ const match = ua.match(/Edg\/([0-9.]+)/);
+ return match ? `Edge ${match[1]}` : 'Edge WebView';
+ } else if (ua.includes('Firefox/')) {
+ // Firefox
+ const match = ua.match(/Firefox\/([0-9.]+)/);
+ return match ? `Firefox ${match[1]}` : 'Firefox Gecko';
+ } else if (ua.includes('Chrome/') && !ua.includes('Chromium')) {
+ // Chrome
+ const match = ua.match(/Chrome\/([0-9.]+)/);
+ return match ? `Chrome ${match[1]}` : 'Chrome';
+ } else if (ua.includes('Chromium/')) {
+ // Chromium
+ const match = ua.match(/Chromium\/([0-9.]+)/);
+ return match ? `Chromium ${match[1]}` : 'Chromium';
+ } else if (ua.includes('MSIE ')) {
+ // Internet Explorer
+ const match = ua.match(/MSIE ([0-9.]+)/);
+ return match ? `IE ${match[1]}` : 'Internet Explorer';
+ } else {
+ return 'Unknown';
+ }
+};