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
12 changes: 8 additions & 4 deletions src/core/currency/change-server-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function connectChangeServer(
callbacks: ChangeServerCallbacks
): ChangeServerConnection {
let ws: WebSocket
let closing = false
function makeWs(): void {
ws = new WebSocket(url)
ws.binaryType = 'arraybuffer'
Expand All @@ -38,15 +39,17 @@ export function connectChangeServer(
out.connected = false
codec.handleClose()
callbacks.handleDisconnect()
// Reconnect after 5 seconds, unless intentionally closed:
if (!closing) {
setTimeout(() => {
makeWs()
}, 5000)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reconnection timeout not cancelled when close() is called

Medium Severity

The closing flag is only checked when the close event fires, not when the scheduled reconnection callback executes. If a WebSocket disconnects unexpectedly and then close() is called before the 5-second reconnect timeout fires, the reconnection will still happen because the setTimeout callback calls makeWs() unconditionally. The callback needs to check if (!closing) before calling makeWs().

Fix in Cursor Fix in Web

}
})

ws.addEventListener('error', errEvent => {
console.error('changeServer websocket error:', errEvent)
ws.close()
// Reconnect after 5 seconds:
setTimeout(() => {
makeWs()
}, 5000)
})

ws.addEventListener('open', () => {
Expand Down Expand Up @@ -87,6 +90,7 @@ export function connectChangeServer(
},

close() {
closing = true
ws.close()
},

Expand Down
9 changes: 6 additions & 3 deletions src/core/currency/currency-pixie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ export const currency: TamePixie<RootProps> = combinePixies({
wallet.changeServiceSubscriptions.some(
subscription =>
subscription.status === 'subscribing' ||
subscription.status === 'resubscribing'
subscription.status === 'resubscribing' ||
subscription.status === 'reconnecting'
)
)
const indexToWalletId: Array<{
Expand Down Expand Up @@ -292,7 +293,8 @@ export const currency: TamePixie<RootProps> = combinePixies({
.subscribe(subscribeParams)
.catch(err => {
input.props.log(`Failed to subscribe: ${String(err)}`)
return [0] as SubscribeResult[]
// Return failure result for each subscription in the batch:
return subscribeParams.map(() => 0 as SubscribeResult)
})
results.push(...r)
}
Expand Down Expand Up @@ -347,7 +349,8 @@ export const currency: TamePixie<RootProps> = combinePixies({
subscription =>
subscription.status === 'subscribing' ||
subscription.status === 'subscribingSlowly' ||
subscription.status === 'resubscribing'
subscription.status === 'resubscribing' ||
subscription.status === 'reconnecting'
)
.map(subscription => ({
...subscription,
Expand Down
27 changes: 19 additions & 8 deletions src/core/currency/wallet/currency-wallet-callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,15 +296,26 @@ export function makeCurrencyWalletCallbacks(
onSubscribeAddresses(
paramsOrAddresses: EdgeSubscribedAddress[] | string[]
) {
// Get existing subscriptions to preserve checkpoints
const existingSubscriptions =
input.props.walletState.changeServiceSubscriptions

const subscribedAddresses: EdgeSubscribedAddress[] =
paramsOrAddresses.map(param =>
typeof param === 'string'
? {
address: param,
checkpoint: undefined
}
: param
)
paramsOrAddresses.map(param => {
const address = typeof param === 'string' ? param : param.address
// Preserve existing checkpoint if we have one for this address.
// Use case-insensitive comparison for blockchain addresses.
const existing = existingSubscriptions.find(
sub => sub.address.toLowerCase() === address.toLowerCase()
)
// Prefer explicit checkpoint from param, fall back to existing
const explicitCheckpoint =
typeof param === 'object' ? param.checkpoint : undefined
return {
address,
checkpoint: explicitCheckpoint ?? existing?.checkpoint
}
})
// Save subscribed addresses to disk (along with current checkpoint)
saveSeenTxCheckpointFile(
input,
Expand Down
14 changes: 13 additions & 1 deletion src/core/currency/wallet/currency-wallet-pixie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
loadSeenTxCheckpointFile,
loadTokensFile,
loadTxFileNames,
saveSeenTxCheckpointFile,
writeTokensFile
} from './currency-wallet-files'
import { CurrencyWalletState, initialTokenIds } from './currency-wallet-reducer'
Expand Down Expand Up @@ -273,7 +274,7 @@ export const walletPixie: TamePixie<CurrencyWalletProps> = combinePixies({
),

syncNetworkUpdate: filterPixie(
(_input: CurrencyWalletInput) => {
(input: CurrencyWalletInput) => {
return {
async update(props) {
if (props.walletOutput == null) return
Expand Down Expand Up @@ -308,6 +309,17 @@ export const walletPixie: TamePixie<CurrencyWalletProps> = combinePixies({
walletId: props.walletId
}
})
// Persist the updated checkpoints to disk:
const subscribedAddresses =
walletState.changeServiceSubscriptions.map(subscription => ({
address: subscription.address,
checkpoint: subscription.checkpoint
}))
await saveSeenTxCheckpointFile(
input,
walletState.seenTxCheckpoint ?? undefined,
subscribedAddresses
)
},
destroy() {}
}
Expand Down
Loading