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
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ class ConnectBitboxCubit extends Cubit<BitboxConnectionState> {
}

Future<void> connectToBitbox(sdk.BitboxDevice device) async {
if (state is BitboxConnecting) return;
// Coalesce overlapping scan ticks onto ONE connect attempt: the state
// guard alone is defeated once the flow moves past BitboxConnecting
// (e.g. BitboxCheckHash) while the init future is still pending — a late
// tick then started a second init() on the shared SDK manager and wedged
// pairing (issue #657 P7 F1). `_pendingInit` covers that whole window; it
// is cleared on failure, so a genuine retry still passes.
if (state is BitboxConnecting || _pendingInit != null) return;
emit(BitboxConnecting(device));
try {
// Snapshot any hash from a prior pairing on the same BitboxService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,36 @@ void main() {
verify(() => authService.ensureSignatureFor(any())).called(1);
});

test('a late scan tick does not start a second init while one is pending '
'(issue #657 P7 F1)', () async {
final initCompleter = Completer<bool>();
var pollCount = 0;
when(() => service.getAllUsbDevices()).thenAnswer((_) async => [device]);
when(() => service.init(any())).thenAnswer((_) => initCompleter.future);
when(() => service.getChannelHash()).thenAnswer((_) async {
pollCount++;
return pollCount < 2 ? '' : 'HASH-1';
});

final cubit = makeCubit();
addTearDown(cubit.close);

// First connect runs init() once and settles in BitboxCheckHash — state
// is now past BitboxConnecting while the init future is still pending.
await waitForState<BitboxCheckHash>(cubit);
verify(() => service.init(any())).called(1);

// An overlapping/late scan tick re-invokes connectToBitbox.
await cubit.connectToBitbox(device);

// It must coalesce onto the in-flight init, NOT start a second init on
// the shared SDK manager (which used to wedge pairing).
verifyNever(() => service.init(any()));
expect(cubit.state, isA<BitboxCheckHash>());

initCompleter.complete(true);
});

test('emits BitboxSignatureFailed when the signature capture throws', () async {
var pollCount = 0;
when(() => service.getAllUsbDevices()).thenAnswer((_) async => [device]);
Expand Down
Loading