diff --git a/src/components/Footer.jsx b/src/components/Footer.tsx similarity index 86% rename from src/components/Footer.jsx rename to src/components/Footer.tsx index 3b4da064f..956c3d57a 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.tsx @@ -1,21 +1,30 @@ -import React, { useState, useEffect, useMemo } from 'react' +import { useState, useEffect, useMemo } from 'react' import * as rb from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' import { useSettings, useSettingsDispatch } from '../context/SettingsContext' +import { useServiceInfo } from '../context/ServiceInfoContext' import { useWebsocketState } from '../context/WebsocketContext' import { useCurrentWallet } from '../context/WalletContext' import Sprite from './Sprite' import Cheatsheet from './Cheatsheet' import packageInfo from '../../package.json' +import { isDevMode } from '../constants/debugFeatures' +import { toSemVer } from '../utils' + +const APP_DISPLAY_VERSION = (() => { + const version = toSemVer(packageInfo.version) + return !isDevMode() ? version.raw : `${version.major}.${version.minor}.${version.patch + 1}dev` +})() export default function Footer() { const { t } = useTranslation() const settings = useSettings() + const serviceInfo = useServiceInfo() const settingsDispatch = useSettingsDispatch() const websocketState = useWebsocketState() const currentWallet = useCurrentWallet() - const [websocketConnected, setWebsocketConnected] = useState() + const [websocketConnected, setWebsocketConnected] = useState(false) const [showBetaWarning, setShowBetaWarning] = useState(false) const [showCheatsheet, setShowCheatsheet] = useState(false) @@ -27,7 +36,7 @@ export default function Footer() { }, [websocketState]) useEffect(() => { - let timer + let timer: NodeJS.Timeout // show the cheatsheet once after the first wallet has been created if (cheatsheetEnabled && settings.showCheatsheet) { timer = setTimeout(() => { @@ -46,9 +55,14 @@ export default function Footer() { <rb.Card className="warning-card translate-middle shadow-lg"> <rb.Card.Body> <rb.Card.Title className="text-center mb-3">{t('footer.warning_alert_title')}</rb.Card.Title> - <p className="text-secondary">{t('footer.warning_alert_text')}</p> + <p>{t('footer.warning_alert_text')}</p> + <p className="text-secondary"> + JoinMarket: v{serviceInfo?.server?.version?.raw || 'unknown'} + <br /> + Jam: v{APP_DISPLAY_VERSION} + </p> <div className="text-center mt-3"> - <rb.Button variant="secondary" onClick={() => setShowBetaWarning(false)}> + <rb.Button variant="dark" onClick={() => setShowBetaWarning(false)}> {t('footer.warning_alert_button_ok')} </rb.Button> </div> @@ -100,7 +114,7 @@ export default function Footer() { rel="noopener noreferrer" className="d-flex align-items-center text-secondary" > - v{packageInfo.version} + v{APP_DISPLAY_VERSION} </a> </div> <div className="d-flex gap-2 pe-2"> diff --git a/src/context/ServiceInfoContext.tsx b/src/context/ServiceInfoContext.tsx index c68b36637..6c6355ebd 100644 --- a/src/context/ServiceInfoContext.tsx +++ b/src/context/ServiceInfoContext.tsx @@ -5,6 +5,7 @@ import { useCurrentWallet, useSetCurrentWallet } from './WalletContext' import { useWebsocket } from './WebsocketContext' import { clearSession } from '../session' import { CJ_STATE_TAKER_RUNNING, CJ_STATE_MAKER_RUNNING } from '../constants/config' +import { toSemVer, UNKNOWN_VERSION } from '../utils' import * as Api from '../libs/JmWalletApi' @@ -57,8 +58,6 @@ interface JmGetInfoData { version: string } -const UNKNOWN_VERSION: SemVer = { major: 0, minor: 0, patch: 0, raw: 'unknown' } - type SessionFlag = { sessionActive: boolean } type MakerRunningFlag = { makerRunning: boolean } type CoinjoinInProgressFlag = { coinjoinInProgress: boolean } @@ -89,21 +88,6 @@ type ServiceInfoUpdate = | RescanBlockchainInProgressFlag | ServerInfo -const versionRegex = new RegExp(/^(\d+)\.(\d+)\.(\d+).*$/) -const toSemVer = (data: JmGetInfoData): SemVer => { - const arr = versionRegex.exec(data.version) - if (!arr || arr.length < 4) { - return UNKNOWN_VERSION - } - - return { - major: parseInt(arr[1], 10), - minor: parseInt(arr[2], 10), - patch: parseInt(arr[3], 10), - raw: data.version, - } -} - interface ServiceInfoContextEntry { serviceInfo: ServiceInfo | null reloadServiceInfo: ({ signal }: { signal: AbortSignal }) => Promise<ServiceInfo> @@ -134,7 +118,7 @@ const ServiceInfoProvider = ({ children }: React.PropsWithChildren<{}>) => { .then((data: JmGetInfoData) => { dispatchServiceInfo({ server: { - version: toSemVer(data), + version: toSemVer(data.version), }, }) }) diff --git a/src/utils.test.ts b/src/utils.test.ts index 50127a81b..a2ab28f5e 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,4 +1,4 @@ -import { shortenStringMiddle, percentageToFactor, factorToPercentage } from './utils' +import { shortenStringMiddle, percentageToFactor, factorToPercentage, toSemVer, UNKNOWN_VERSION } from './utils' describe('shortenStringMiddle', () => { it('should shorten string in the middle', () => { @@ -69,3 +69,47 @@ describe('factorToPercentage/percentageToFactor', () => { expect(testInverse(233.7)).toBe(233.7) }) }) + +describe('toSemVer', () => { + it('should parse version correctly', () => { + expect(toSemVer('0.0.1')).toEqual({ + major: 0, + minor: 0, + patch: 1, + raw: '0.0.1', + }) + expect(toSemVer('0.9.11dev')).toEqual({ + major: 0, + minor: 9, + patch: 11, + raw: '0.9.11dev', + }) + expect(toSemVer('1.0.0-beta.2')).toEqual({ + major: 1, + minor: 0, + patch: 0, + raw: '1.0.0-beta.2', + }) + expect(toSemVer('21.42.1337-dev.2+devel.99ff4cd')).toEqual({ + major: 21, + minor: 42, + patch: 1337, + raw: '21.42.1337-dev.2+devel.99ff4cd', + }) + }) + it('should parse invalid version as UNKNOWN', () => { + expect(toSemVer(undefined)).toBe(UNKNOWN_VERSION) + expect(toSemVer('')).toBe(UNKNOWN_VERSION) + expect(toSemVer(' ')).toBe(UNKNOWN_VERSION) + expect(toSemVer('🧡')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21.42')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21.42.')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21.42.💯')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21.42.-1')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21.42.-1')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21.-1.42')).toBe(UNKNOWN_VERSION) + expect(toSemVer('-1.21.42')).toBe(UNKNOWN_VERSION) + expect(toSemVer('21million')).toBe(UNKNOWN_VERSION) + }) +}) diff --git a/src/utils.ts b/src/utils.ts index d02c7eb68..c5979d0a6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -71,3 +71,20 @@ export const factorToPercentage = (val: number, precision = 6) => { } export const isValidNumber = (val: number | undefined) => typeof val === 'number' && !isNaN(val) + +export const UNKNOWN_VERSION: SemVer = { major: 0, minor: 0, patch: 0, raw: 'unknown' } + +const versionRegex = new RegExp(/^v?(\d+)\.(\d+)\.(\d+).*$/) +export const toSemVer = (raw?: string): SemVer => { + const arr = versionRegex.exec(raw || '') + if (!arr || arr.length < 4) { + return UNKNOWN_VERSION + } + + return { + major: parseInt(arr[1], 10), + minor: parseInt(arr[2], 10), + patch: parseInt(arr[3], 10), + raw, + } +} diff --git a/tsconfig.json b/tsconfig.json index 4736e373e..c0a48638b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "strict": true, "jsx": "react-jsx", "skipLibCheck": true, - "allowJs": true + "allowJs": true, + "resolveJsonModule": true } }