Skip to content

Commit ab459d1

Browse files
committed
Fix cancellation support to synchronization
Introduces cancellation functionality to the synchronization process. This allows users to interrupt long-running synchronization tasks. This change includes: - Passing an optional cancellation callback function to Ledger client methods and synchronization service functions. - Checking for cancellation requests before and after Ledger interactions. - Updating tests to reflect the new cancellation parameter. - Moving type definitions for better organization.
1 parent f68b8cf commit ab459d1

File tree

7 files changed

+55
-26
lines changed

7 files changed

+55
-26
lines changed

components/sections/migrate/index-input-section.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { Alert, AlertDescription } from '@/components/ui/alert'
33
import { Button } from '@/components/ui/button'
44
import { Input } from '@/components/ui/input'
55
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
6-
import type { ScanType, RangeField } from '@/lib/types/scan'
7-
import { SCAN_LIMITS, ScanTypeEnum, RangeFieldEnum } from '@/lib/types/scan'
8-
import { adjustIndexValue, getIndexCount, isRangeExceedsLimit, getPluralForm } from '@/lib/utils/scan-indices'
6+
import type { RangeField, ScanType } from '@/lib/types/scan'
7+
import { RangeFieldEnum, SCAN_LIMITS, ScanTypeEnum } from '@/lib/types/scan'
8+
import { adjustIndexValue, getIndexCount, getPluralForm, isRangeExceedsLimit } from '@/lib/utils/scan-indices'
99

1010
interface IndexInputSectionProps {
1111
title: string

lib/services/__tests__/synchronization.service.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('Synchronization Service', () => {
209209

210210
const result = await synchronizeAppAccounts(mockApp, [], true)
211211

212-
expect(ledgerClient.synchronizeAccounts).toHaveBeenCalledWith(mockApp)
212+
expect(ledgerClient.synchronizeAccounts).toHaveBeenCalledWith(mockApp, undefined)
213213
expect(result.app.status).toBe(AppStatus.SYNCHRONIZED)
214214
})
215215

@@ -257,7 +257,7 @@ describe('Synchronization Service', () => {
257257
expect(result.id).toBe(mockApp.id)
258258
expect(result.name).toBe(mockApp.name)
259259
expect(result.status).toBe(AppStatus.SYNCHRONIZED)
260-
expect(ledgerClient.synchronizeAccountsWithIndices).toHaveBeenCalledWith(mockApp, accountIndices, addressIndices)
260+
expect(ledgerClient.synchronizeAccountsWithIndices).toHaveBeenCalledWith(mockApp, accountIndices, addressIndices, undefined)
261261
expect(processAccountsForApp).toHaveBeenCalledWith(
262262
mockSyncResult.result,
263263
mockApp,
@@ -361,7 +361,7 @@ describe('Synchronization Service', () => {
361361
expect(result.id).toBe('polkadot')
362362
expect(result.name).toBe('Polkadot')
363363
expect(result.status).toBe(AppStatus.SYNCHRONIZED)
364-
expect(ledgerClient.synchronizeAccounts).toHaveBeenCalledWith(mockApp)
364+
expect(ledgerClient.synchronizeAccounts).toHaveBeenCalledWith(mockApp, undefined)
365365
})
366366

367367
it('should handle errors during Polkadot synchronization', async () => {

lib/services/synchronization.service.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@ function getAppsToSync(): AppConfig[] {
6666
* for the specified blockchain application. This is the first step in the synchronization process.
6767
*
6868
* @param {AppConfig} appConfig - The blockchain application configuration
69+
* @param {() => boolean} [onCancel] - Optional cancellation callback function
6970
* @returns {Promise<Address[]>} Array of addresses retrieved from the Ledger device
7071
* @throws {InternalError} When Ledger communication fails or returns no results
7172
*/
72-
async function fetchAddressesFromLedger(appConfig: AppConfig): Promise<Address[]> {
73+
async function fetchAddressesFromLedger(appConfig: AppConfig, onCancel?: () => boolean): Promise<Address[]> {
7374
try {
74-
const response = await ledgerClient.synchronizeAccounts(appConfig)
75+
const response = await ledgerClient.synchronizeAccounts(appConfig, onCancel)
7576

7677
if (!response.result) {
7778
throw new InternalError(InternalErrorType.SYNC_ERROR, {
@@ -108,14 +109,21 @@ async function fetchAddressesFromLedger(appConfig: AppConfig): Promise<Address[]
108109
export async function synchronizeAppAccounts(
109110
appConfig: AppConfig,
110111
polkadotAddresses: string[],
111-
filterByBalance = true
112+
filterByBalance = true,
113+
onCancel?: () => boolean
112114
): Promise<{
113115
app: App
114116
polkadotAddressesForApp: string[]
115117
}> {
116118
try {
119+
// Check for cancellation before starting
120+
checkCancellation(onCancel)
121+
117122
// Fetch addresses from Ledger
118-
const addresses = await fetchAddressesFromLedger(appConfig)
123+
const addresses = await fetchAddressesFromLedger(appConfig, onCancel)
124+
125+
// Check for cancellation after fetching addresses
126+
checkCancellation(onCancel)
119127

120128
if (!appConfig.rpcEndpoints || appConfig.rpcEndpoints.length === 0) {
121129
throw new InternalError(InternalErrorType.SYNC_ERROR, {
@@ -236,10 +244,10 @@ export async function synchronizeAppAccounts(
236244
* const destinationAddresses = polkadotApp.accounts?.map(acc => acc.address) || []
237245
* ```
238246
*/
239-
export async function synchronizePolkadotAccounts(): Promise<App> {
247+
export async function synchronizePolkadotAccounts(onCancel?: () => boolean): Promise<App> {
240248
try {
241249
const appConfig = polkadotAppConfig
242-
const response = await ledgerClient.synchronizeAccounts(appConfig)
250+
const response = await ledgerClient.synchronizeAccounts(appConfig, onCancel)
243251

244252
const noAccountsNotification = {
245253
title: 'No Polkadot accounts found',
@@ -339,12 +347,13 @@ export async function synchronizePolkadotAccounts(): Promise<App> {
339347
export async function synchronizeSingleApp(
340348
appConfig: AppConfig,
341349
polkadotAddresses: string[],
342-
filterByBalance = true
350+
filterByBalance = true,
351+
onCancel?: () => boolean
343352
): Promise<{
344353
app: App
345354
polkadotAddresses: string[]
346355
}> {
347-
const { app, polkadotAddressesForApp } = await synchronizeAppAccounts(appConfig, polkadotAddresses, filterByBalance)
356+
const { app, polkadotAddressesForApp } = await synchronizeAppAccounts(appConfig, polkadotAddresses, filterByBalance, onCancel)
348357
return {
349358
app,
350359
polkadotAddresses: polkadotAddressesForApp,
@@ -379,7 +388,7 @@ export async function scanAppWithCustomIndices(
379388
checkCancellation(onCancel)
380389

381390
// Fetch addresses from Ledger using custom indices
382-
const addresses = await ledgerClient.synchronizeAccountsWithIndices(appConfig, accountIndices, addressIndices)
391+
const addresses = await ledgerClient.synchronizeAccountsWithIndices(appConfig, accountIndices, addressIndices, onCancel)
383392

384393
// Check for cancellation after Ledger interaction
385394
checkCancellation(onCancel)
@@ -507,7 +516,7 @@ export async function synchronizeAllApps(
507516
})
508517

509518
// Synchronize Polkadot accounts first
510-
const polkadotApp = await synchronizePolkadotAccounts()
519+
const polkadotApp = await synchronizePolkadotAccounts(onCancel)
511520
const polkadotAddresses = polkadotApp.accounts?.map(account => account.address) || []
512521

513522
// Get apps to synchronize (exclude Polkadot since it's handled separately)
@@ -552,7 +561,12 @@ export async function synchronizeAllApps(
552561
onAppStart?.(loadingApp)
553562

554563
try {
555-
const { app, polkadotAddressesForApp: appPolkadotAddresses } = await synchronizeAppAccounts(appConfig, polkadotAddresses)
564+
const { app, polkadotAddressesForApp: appPolkadotAddresses } = await synchronizeAppAccounts(
565+
appConfig,
566+
polkadotAddresses,
567+
true,
568+
onCancel
569+
)
556570
synchronizedApps.push(app)
557571
polkadotAddressesForApp[app.id] = appPolkadotAddresses
558572

lib/utils/scan-indices.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { IndexConfig, RangeField, RangeIndexConfig, ScanType, SingleIndexConfig, ValidationResult } from '@/lib/types/scan'
2-
import { SCAN_LIMITS, ScanTypeEnum, RangeFieldEnum } from '@/lib/types/scan'
2+
import { RangeFieldEnum, SCAN_LIMITS, ScanTypeEnum } from '@/lib/types/scan'
33

44
/**
55
* Type guard to check if scan type is 'single'

state/client/__tests__/ledger.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ describe('Ledger Client', () => {
206206
.mockResolvedValueOnce(mockAddresses[1])
207207
.mockResolvedValueOnce(mockAddresses[2])
208208

209-
const result = await ledgerClient.synchronizeAccounts(mockAppConfig)
209+
const result = await ledgerClient.synchronizeAccounts(mockAppConfig, undefined)
210210

211211
expect(result).toEqual({
212212
result: [
@@ -226,7 +226,7 @@ describe('Ledger Client', () => {
226226
.mockResolvedValueOnce(undefined)
227227
.mockRejectedValueOnce(new Error('Address fetch failed'))
228228

229-
const result = await ledgerClient.synchronizeAccounts(mockAppConfig)
229+
const result = await ledgerClient.synchronizeAccounts(mockAppConfig, undefined)
230230

231231
expect(result).toEqual({
232232
result: [{ ...mockAddress, path: "m/44'/354'/0'/0'/0'" }],
@@ -236,7 +236,7 @@ describe('Ledger Client', () => {
236236
it('should handle ledger service errors', async () => {
237237
vi.mocked(ledgerService.getAccountAddress).mockRejectedValue(new Error('Ledger error'))
238238

239-
await expect(ledgerClient.synchronizeAccounts(mockAppConfig)).rejects.toThrow(InternalError)
239+
await expect(ledgerClient.synchronizeAccounts(mockAppConfig, undefined)).rejects.toThrow(InternalError)
240240
})
241241
})
242242

state/client/ledger.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,22 @@ export const ledgerClient = {
6464
})
6565
},
6666

67-
async synchronizeAccounts(app: AppConfig): Promise<{ result?: Address[] }> {
67+
async synchronizeAccounts(app: AppConfig, onCancel?: () => boolean): Promise<{ result?: Address[] }> {
6868
return withErrorHandling(
6969
async () => {
7070
// fetch addresses
7171
const addresses: Address[] = []
7272
for (let i = 0; i < maxAddressesToFetch; i++) {
73+
// Check for cancellation before fetching each address
74+
if (onCancel?.()) {
75+
return { result: undefined }
76+
}
77+
7378
try {
7479
const derivedPath = updateBip44PathIndices(app.bip44Path, { address: i })
7580
const address = await ledgerService.getAccountAddress(derivedPath, app.ss58Prefix, false)
76-
if (address) {
81+
if (address && !onCancel?.()) {
82+
// Double-check cancellation before adding
7783
addresses.push({ ...address, path: derivedPath } as Address)
7884
}
7985
} catch {
@@ -100,7 +106,8 @@ export const ledgerClient = {
100106
async synchronizeAccountsWithIndices(
101107
app: AppConfig,
102108
accountIndices: number[],
103-
addressIndices: number[]
109+
addressIndices: number[],
110+
onCancel?: () => boolean
104111
): Promise<{ result?: Address[] }> {
105112
return withErrorHandling(
106113
async () => {
@@ -109,6 +116,11 @@ export const ledgerClient = {
109116
// Process accounts and addresses sequentially
110117
for (const accountIndex of accountIndices) {
111118
for (const addressIndex of addressIndices) {
119+
// Check for cancellation before fetching each address
120+
if (onCancel?.()) {
121+
throw new Error('Synchronization was cancelled')
122+
}
123+
112124
try {
113125
// Build the derivation path with both account and address indices using the robust utility
114126
const derivedPath = updateBip44PathIndices(app.bip44Path, {
@@ -117,7 +129,8 @@ export const ledgerClient = {
117129
})
118130

119131
const address = await ledgerService.getAccountAddress(derivedPath, app.ss58Prefix, false)
120-
if (address) {
132+
if (address && !onCancel?.()) {
133+
// Double-check cancellation before adding
121134
addresses.push({ ...address, path: derivedPath } as Address)
122135
}
123136
} catch (error) {

state/ledger.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,9 @@ export const ledgerState$ = observable({
381381
const polkadotAccounts = ledgerState$.apps.polkadotApp.get().accounts || []
382382
const polkadotAddresses = polkadotAccounts.map(account => account.address)
383383

384-
const { app, polkadotAddressesForApp } = await synchronizeAppAccounts(appConfig, polkadotAddresses)
384+
const { app, polkadotAddressesForApp } = await synchronizeAppAccounts(appConfig, polkadotAddresses, true, () =>
385+
ledgerState$.apps.isSyncCancelRequested.get()
386+
)
385387
if (app) {
386388
updateApp(appId, app)
387389
ledgerState$.polkadotAddresses[appId].set(polkadotAddressesForApp)

0 commit comments

Comments
 (0)