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
17 changes: 17 additions & 0 deletions packages/neuron-wallet/src/controllers/app/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import startMonitor, { stopMonitor } from '../../services/monitor'
import { clearCkbNodeCache } from '../../services/ckb-runner'
import ShowGlobalDialogSubject from '../../models/subjects/show-global-dialog'
import NoDiskSpaceSubject from '../../models/subjects/no-disk-space'
import CkbDependencySubject from '../../models/subjects/ckb-dependency'
import { showCkbDependencyDialog } from '../../utils/ckb-dependency-dialog'

interface AppResponder {
sendMessage: (channel: string, arg: any) => void
Expand All @@ -25,6 +27,8 @@ interface AppResponder {
updateWindowTitle: () => void
}

let isShowingCkbDependencyDialog = false

/**
* subscribe to events and dispatch them to the renderer process
*/
Expand Down Expand Up @@ -127,4 +131,17 @@ export const subscribe = (dispatcher: AppResponder) => {
stopMonitor()
dispatcher.sendMessage('no-disk-space', params)
})

CkbDependencySubject.subscribe(async () => {
if (isShowingCkbDependencyDialog) {
return
}
isShowingCkbDependencyDialog = true
try {
stopMonitor('ckb')
await showCkbDependencyDialog()
} finally {
isShowingCkbDependencyDialog = false
}
Comment on lines +140 to +145
})
}
2 changes: 1 addition & 1 deletion packages/neuron-wallet/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export default {
'ckb-dependency': {
title: 'Bundled CKB Node',
message: 'Dependency Required',
detail: `The network nodes in Neuron rely on C++ components, so please install the latest version of Microsoft Visual C++Redistributable for x64 to ensure that the software runs properly.`,
detail: `The bundled CKB node in Neuron requires the latest Microsoft Visual C++ Redistributable for x64. The installed version is missing or too old, so please install the latest version to ensure that the software runs properly.`,
buttons: {
'install-and-exit': 'Install and Exit',
},
Expand Down
2 changes: 1 addition & 1 deletion packages/neuron-wallet/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export default {
title: '内置 CKB 节点',
message: '缺少必要的依赖',
detail:
'Neuron 中的网络节点依赖C++组件,请安装 x64 最新版本的 Microsoft Visual C++Redistributable 来保证软件正常运行。',
'Neuron 的内置 CKB 节点需要最新 x64 版本的 Microsoft Visual C++ Redistributable。当前版本缺失或过旧,请安装最新版以保证软件正常运行。',
buttons: {
'install-and-exit': '安装并退出',
},
Expand Down
5 changes: 5 additions & 0 deletions packages/neuron-wallet/src/models/subjects/ckb-dependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Subject } from 'rxjs'

const CkbDependencySubject = new Subject<void>()

export default CkbDependencySubject
8 changes: 8 additions & 0 deletions packages/neuron-wallet/src/services/ckb-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getUsablePort } from '../utils/get-usable-port'
import { updateToml } from '../utils/toml'
import { BUNDLED_URL_PREFIX } from '../utils/const'
import NoDiskSpaceSubject from '../models/subjects/no-disk-space'
import CkbDependencySubject from '../models/subjects/ckb-dependency'

const platform = (): string => {
switch (process.platform) {
Expand All @@ -31,6 +32,10 @@ enum NeedMigrateMsg {
Recommends = 'CKB recommends migrating your data into a new format',
}

export const isVcredistVersionError = (message: string) => {
return /(?:VC\+\+|Visual C\+\+) Redistributable/i.test(message) && /Version is below|download\/upgrade/i.test(message)
}
Comment on lines +35 to +37

const { app } = env
let ckb: ChildProcess | null = null

Expand Down Expand Up @@ -132,6 +137,9 @@ export const startCkbNode = async () => {
if (dataString.includes(NeedMigrateMsg.Wants) || dataString.includes(NeedMigrateMsg.Recommends)) {
MigrateSubject.next({ type: 'need-migrate' })
}
if (isVcredistVersionError(dataString)) {
CkbDependencySubject.next()
}
})
currentProcess.stdout?.on('data', data => {
const dataString: string = data.toString()
Expand Down
24 changes: 3 additions & 21 deletions packages/neuron-wallet/src/services/node.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import fs from 'fs'
import path from 'path'
import { BI } from '@ckb-lumos/lumos'
import { app as electronApp, dialog, shell, app } from 'electron'
import { t } from 'i18next'
import { app as electronApp, app } from 'electron'
import { interval, BehaviorSubject, merge, Subject } from 'rxjs'
import { distinctUntilChanged, sampleTime, flatMap, delay, retry, debounceTime } from 'rxjs/operators'
import env from '../env'
import { ConnectionStatusSubject } from '../models/subjects/node'
import { CurrentNetworkIDSubject } from '../models/subjects/networks'
import { NetworkType } from '../models/network'
Expand All @@ -17,6 +15,7 @@ import logger from '../utils/logger'
import redistCheck from '../utils/redist-check'
import { rpcRequest } from '../utils/rpc-request'
import { generateRPC } from '../utils/ckb-rpc'
import { showCkbDependencyDialog } from '../utils/ckb-dependency-dialog'
import startMonitor, { stopMonitor } from './monitor'
import { CKBLightRunner } from './light-runner'
import SettingsService from './settings'
Expand Down Expand Up @@ -211,24 +210,7 @@ class NodeService {
}

private showGuideDialog = () => {
const I18N_PATH = `messageBox.ckb-dependency`
return dialog
.showMessageBox({
type: 'info',
buttons: ['install-and-exit'].map(label => t(`${I18N_PATH}.buttons.${label}`)),
defaultId: 0,
title: t(`${I18N_PATH}.title`),
message: t(`${I18N_PATH}.message`),
detail: t(`${I18N_PATH}.detail`),
cancelId: 0,
noLink: true,
})
.then(() => {
const VC_REDIST_URL = `https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170`
shell.openExternal(VC_REDIST_URL)
env.app.quit()
return false
})
return showCkbDependencyDialog()
}

private getInternalNodeVersion(type: CKBNodeType) {
Expand Down
26 changes: 26 additions & 0 deletions packages/neuron-wallet/src/utils/ckb-dependency-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { dialog, shell } from 'electron'
import { t } from 'i18next'

import env from '../env'

export const VC_REDIST_URL = 'https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170'

export const showCkbDependencyDialog = () => {
const I18N_PATH = `messageBox.ckb-dependency`
return dialog
.showMessageBox({
type: 'info',
buttons: ['install-and-exit'].map(label => t(`${I18N_PATH}.buttons.${label}`)),
defaultId: 0,
title: t(`${I18N_PATH}.title`),
message: t(`${I18N_PATH}.message`),
detail: t(`${I18N_PATH}.detail`),
cancelId: 0,
noLink: true,
})
.then(() => {
shell.openExternal(VC_REDIST_URL)
env.app.quit()
return false
})
}
34 changes: 33 additions & 1 deletion packages/neuron-wallet/src/utils/redist-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const redistCheck = async () => {
logger.error(`${query} stderr: ${stderr}`)
return false
}
return !!stdout
return isSupportedRedist(stdout)
})
.catch(err => {
logger.error(err)
Expand All @@ -33,4 +33,36 @@ const redistCheck = async () => {
return vcredists.includes(true)
}

export const MINIMUM_REDIST_VERSION = '14.44.0.0'

export const getRedistVersion = (stdout?: string | null) => {
return stdout
?.split(/\r?\n/)
.find(line => /\bVersion\b/i.test(line))
?.match(/v?(\d+(?:\.\d+)+)/i)?.[1]
}

export const compareVersions = (version: string, minimumVersion: string) => {
const versionParts = version.split('.').map(Number)
const minimumVersionParts = minimumVersion.split('.').map(Number)
const length = Math.max(versionParts.length, minimumVersionParts.length)
for (let idx = 0; idx < length; idx++) {
const current = versionParts[idx] ?? 0
const minimum = minimumVersionParts[idx] ?? 0
if (current > minimum) {
return 1
}
if (current < minimum) {
return -1
}
}
return 0
}

export const isSupportedRedist = (stdout?: string | null) => {
const installed = !/\bInstalled\s+REG_DWORD\s+0x0\b/i.test(stdout ?? '')
const version = getRedistVersion(stdout)
return installed && !!version && compareVersions(version, MINIMUM_REDIST_VERSION) >= 0
}

export default redistCheck
36 changes: 35 additions & 1 deletion packages/neuron-wallet/tests/services/ckb-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const stubbedLoggerLog = jest.fn()
const resetSyncTaskQueueAsyncPushMock = jest.fn()
const updateTomlMock = jest.fn()
const getUsablePortMock = jest.fn()
const mockCkbDependencyNext = jest.fn()

const stubbedProcess: any = {}

Expand All @@ -24,6 +25,7 @@ const resetMocks = () => {
resetSyncTaskQueueAsyncPushMock.mockReset()
updateTomlMock.mockReset()
getUsablePortMock.mockReset()
mockCkbDependencyNext.mockReset()
}

jest.doMock('child_process', () => {
Expand Down Expand Up @@ -91,7 +93,16 @@ jest.doMock('../../src/utils/toml', () => ({
jest.doMock('../../src/utils/get-usable-port', () => ({
getUsablePort: getUsablePortMock,
}))
const { startCkbNode, stopCkbNode, migrateCkbData, getNodeUrl } = require('../../src/services/ckb-runner')
jest.mock('../../src/models/subjects/ckb-dependency', () => ({
next: mockCkbDependencyNext,
}))
const {
startCkbNode,
stopCkbNode,
migrateCkbData,
getNodeUrl,
isVcredistVersionError,
} = require('../../src/services/ckb-runner')

describe('ckb runner', () => {
let stubbedCkb: any = new EventEmitter()
Expand Down Expand Up @@ -141,6 +152,16 @@ describe('ckb runner', () => {
{ stdio: ['ignore', 'pipe', 'pipe'] }
)
})

it('notifies when ckb reports an outdated Visual C++ Redistributable', () => {
stubbedCkb.stderr.emit(
'data',
Buffer.from(
'Detected VC++ Redistributable version (x64): v14.29.30133.00\nVersion is below 14.44.0.0. Please download/upgrade the Visual C++ Redistributable.'
)
)
expect(mockCkbDependencyNext).toHaveBeenCalled()
})
})

describe('without config file', () => {
Expand Down Expand Up @@ -238,6 +259,19 @@ describe('ckb runner', () => {
})
})
})
describe('#isVcredistVersionError', () => {
it('detects the bundled ckb Visual C++ Redistributable version error', () => {
expect(
isVcredistVersionError(
'Detected VC++ Redistributable version (x64): v14.29.30133.00\nVersion is below 14.44.0.0. Please download/upgrade the Visual C++ Redistributable.'
)
).toBe(true)
})

it('ignores unrelated ckb stderr', () => {
expect(isVcredistVersionError('CKB wants to migrate the data into new format')).toBe(false)
})
})
describe('#stopCkbNode', () => {
beforeEach(async () => {
stubbedExistsSync.mockReturnValue(true)
Expand Down
16 changes: 16 additions & 0 deletions packages/neuron-wallet/tests/services/node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('NodeService', () => {
const pathJoinMock = jest.fn()
const redistCheckMock = jest.fn()
const isFirstSyncMock = jest.fn()
const showCkbDependencyDialogMock = jest.fn()

const fakeHTTPUrl = 'http://fakeurl'

Expand Down Expand Up @@ -59,6 +60,7 @@ describe('NodeService', () => {
pathJoinMock.mockReset()
redistCheckMock.mockReset()
isFirstSyncMock.mockReset()
showCkbDependencyDialogMock.mockReset()
}

beforeEach(() => {
Expand Down Expand Up @@ -184,6 +186,10 @@ describe('NodeService', () => {

jest.doMock('utils/redist-check', () => redistCheckMock)

jest.doMock('utils/ckb-dependency-dialog', () => ({
showCkbDependencyDialog: showCkbDependencyDialogMock,
}))

jest.doMock('services/settings', () => ({
getInstance() {
return {
Expand Down Expand Up @@ -595,5 +601,15 @@ describe('NodeService', () => {
`CKB:\texternal RPC on default uri not detected, starting bundled CKB node.`
)
})
it('shows the CKB dependency dialog when vcredist is missing or outdated', async () => {
isFirstSyncMock.mockReturnValue(false)
redistCheckMock.mockResolvedValue(false)
showCkbDependencyDialogMock.mockResolvedValue(false)

await nodeService.tryStartNodeOnDefaultURI()

expect(showCkbDependencyDialogMock).toHaveBeenCalled()
expect(stubbedStartCKBNode).not.toHaveBeenCalled()
})
})
})
Loading
Loading