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 = () => { >
-
+
App Logo
-

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'; + } +};