Skip to content
Merged
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
2 changes: 1 addition & 1 deletion packages/connect-examples/electron-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "hardware-example",
"productName": "HardwareExample",
"executableName": "onekey-hardware-example",
"version": "1.1.4-alpha.4",
"version": "1.1.5",
"author": "OneKey",
"description": "End-to-end encrypted workspaces for teams",
"main": "dist/index.js",
Expand Down
10 changes: 5 additions & 5 deletions packages/connect-examples/expo-example/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "expo-example",
"version": "1.1.4-alpha.4",
"version": "1.1.5",
"scripts": {
"start": "cross-env CONNECT_SRC=https://localhost:8087/ yarn expo start --dev-client",
"android": "yarn expo run:android",
Expand All @@ -19,10 +19,10 @@
"@noble/ed25519": "^2.1.0",
"@noble/hashes": "^1.3.3",
"@noble/secp256k1": "^1.7.1",
"@onekeyfe/hd-ble-sdk": "1.1.4-alpha.4",
"@onekeyfe/hd-common-connect-sdk": "1.1.4-alpha.4",
"@onekeyfe/hd-core": "1.1.4-alpha.4",
"@onekeyfe/hd-web-sdk": "1.1.4-alpha.4",
"@onekeyfe/hd-ble-sdk": "1.1.5",
"@onekeyfe/hd-common-connect-sdk": "1.1.5",
"@onekeyfe/hd-core": "1.1.5",
"@onekeyfe/hd-web-sdk": "1.1.5",
"@onekeyfe/react-native-ble-utils": "^0.1.3",
"@polkadot/util-crypto": "13.1.1",
"@react-native-async-storage/async-storage": "1.21.0",
Expand Down
8 changes: 4 additions & 4 deletions packages/connect-examples/expo-playground/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/onekey-hardware-playground",
"version": "1.1.4-alpha.4",
"version": "1.1.5",
"private": true,
"sideEffects": [
"app/utils/shim.js",
Expand All @@ -17,9 +17,9 @@
},
"dependencies": {
"@noble/hashes": "^1.8.0",
"@onekeyfe/hd-core": "1.1.4-alpha.4",
"@onekeyfe/hd-shared": "1.1.4-alpha.4",
"@onekeyfe/hd-web-sdk": "1.1.4-alpha.4",
"@onekeyfe/hd-core": "1.1.5",
"@onekeyfe/hd-shared": "1.1.5",
"@onekeyfe/hd-web-sdk": "1.1.5",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
Expand Down
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/hd-core",
"version": "1.1.4-alpha.4",
"version": "1.1.5",
"description": "> TODO: description",
"author": "OneKey",
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
Expand All @@ -25,8 +25,8 @@
"url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
},
"dependencies": {
"@onekeyfe/hd-shared": "1.1.4-alpha.4",
"@onekeyfe/hd-transport": "1.1.4-alpha.4",
"@onekeyfe/hd-shared": "1.1.5",
"@onekeyfe/hd-transport": "1.1.5",
"axios": "^0.27.2",
"bignumber.js": "^9.0.2",
"bytebuffer": "^5.0.1",
Expand Down
8 changes: 4 additions & 4 deletions packages/hd-ble-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/hd-ble-sdk",
"version": "1.1.4-alpha.4",
"version": "1.1.5",
"author": "OneKey",
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
"license": "ISC",
Expand All @@ -20,8 +20,8 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@onekeyfe/hd-core": "1.1.4-alpha.4",
"@onekeyfe/hd-shared": "1.1.4-alpha.4",
"@onekeyfe/hd-transport-react-native": "1.1.4-alpha.4"
"@onekeyfe/hd-core": "1.1.5",
"@onekeyfe/hd-shared": "1.1.5",
"@onekeyfe/hd-transport-react-native": "1.1.5"
}
}
14 changes: 7 additions & 7 deletions packages/hd-common-connect-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/hd-common-connect-sdk",
"version": "1.1.4-alpha.4",
"version": "1.1.5",
"author": "OneKey",
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
"license": "ISC",
Expand All @@ -20,11 +20,11 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@onekeyfe/hd-core": "1.1.4-alpha.4",
"@onekeyfe/hd-shared": "1.1.4-alpha.4",
"@onekeyfe/hd-transport-emulator": "1.1.4-alpha.4",
"@onekeyfe/hd-transport-http": "1.1.4-alpha.4",
"@onekeyfe/hd-transport-lowlevel": "1.1.4-alpha.4",
"@onekeyfe/hd-transport-web-device": "1.1.4-alpha.4"
"@onekeyfe/hd-core": "1.1.5",
"@onekeyfe/hd-shared": "1.1.5",
"@onekeyfe/hd-transport-emulator": "1.1.5",
"@onekeyfe/hd-transport-http": "1.1.5",
"@onekeyfe/hd-transport-lowlevel": "1.1.5",
"@onekeyfe/hd-transport-web-device": "1.1.5"
}
}
4 changes: 2 additions & 2 deletions packages/hd-transport-electron/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@onekeyfe/hd-transport-electron",
"version": "1.1.4-alpha.4",
"version": "1.1.5",
"author": "OneKey",
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
"license": "MIT",
Expand All @@ -25,7 +25,7 @@
"electron-log": ">=4.0.0"
},
"dependencies": {
"@onekeyfe/hd-shared": "1.1.4-alpha.4"
"@onekeyfe/hd-shared": "1.1.5"
},
"devDependencies": {
"@types/web-bluetooth": "^0.0.17",
Expand Down
190 changes: 180 additions & 10 deletions packages/hd-transport-electron/src/noble-ble-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { COMMON_HEADER_SIZE } from '@onekeyfe/hd-transport';
import type { WebContents, IpcMainInvokeEvent } from 'electron';
import type { Peripheral, Service, Characteristic } from '@abandonware/noble';
import pRetry from 'p-retry';
import type { NobleModule, Logger, DeviceInfo, CharacteristicPair } from './types/noble-extended';
import { safeLog } from './types/noble-extended';

Expand Down Expand Up @@ -250,6 +251,9 @@ async function initializeNoble(): Promise<void> {
return;
}

// Setup persistent state listener before initialization
setupPersistentStateListener();

const timeout = setTimeout(() => {
reject(
ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Bluetooth initialization timeout')
Expand Down Expand Up @@ -289,9 +293,6 @@ async function initializeNoble(): Promise<void> {
handleDeviceDiscovered(peripheral);
});

// Setup persistent state listener after initialization
setupPersistentStateListener();

logger?.info('[NobleBLE] Noble initialized successfully');
} catch (error) {
logger?.error('[NobleBLE] Failed to initialize Noble:', error);
Expand Down Expand Up @@ -563,7 +564,7 @@ function getDevice(deviceId: string): DeviceInfo | null {
};
}

// Discover services and characteristics for a connected device
// Core service discovery function (single attempt)
async function discoverServicesAndCharacteristics(
peripheral: Peripheral
): Promise<CharacteristicPair> {
Expand Down Expand Up @@ -642,6 +643,168 @@ async function discoverServicesAndCharacteristics(
});
}

// Force reconnect to clear potential connection state issues
async function forceReconnectPeripheral(peripheral: Peripheral, deviceId: string): Promise<void> {
logger?.info('[NobleBLE] Forcing connection reset for device:', deviceId);

// Step 1: Force disconnect if connected
if (peripheral.state === 'connected') {
await new Promise<void>(resolve => {
peripheral.disconnect(() => {
logger?.info('[NobleBLE] Force disconnect completed');
resolve();
});
});

// Wait for complete disconnection
await wait(1000);
}

// Step 2: Clear device state
connectedDevices.delete(deviceId);
deviceCharacteristics.delete(deviceId);
devicePacketStates.delete(deviceId);
subscribedDevices.delete(deviceId);
subscriptionOperations.delete(deviceId);

// Step 3: Re-establish connection
await new Promise<void>((resolve, reject) => {
peripheral.connect((error: string) => {
if (error) {
logger?.error('[NobleBLE] Force reconnect failed:', error);
reject(new Error(`Force reconnect failed: ${error}`));
} else {
logger?.info('[NobleBLE] Force reconnect successful');
connectedDevices.set(deviceId, peripheral);
resolve();
}
});
});

// Wait for connection to stabilize
await wait(500);
}

// Enhanced connection with fresh peripheral rescan as last resort
async function connectAndDiscoverWithFreshScan(deviceId: string): Promise<CharacteristicPair> {
logger?.info('[NobleBLE] Attempting connection with fresh peripheral scan as fallback');

const currentPeripheral = discoveredDevices.get(deviceId);

// First attempt with existing peripheral
if (currentPeripheral) {
try {
return await discoverServicesAndCharacteristicsWithRetry(currentPeripheral, deviceId);
} catch (error) {
logger?.error(
'[NobleBLE] Service discovery failed with existing peripheral, attempting fresh scan...'
);
}
}

// Last resort: Fresh scan to get new peripheral object
logger?.info(
'[NobleBLE] Performing fresh scan to get new peripheral object for device:',
deviceId
);

try {
const freshPeripheral = await performTargetedScan(deviceId);
if (!freshPeripheral) {
throw new Error(`Device ${deviceId} not found in fresh scan`);
}

// Update device maps with fresh peripheral
discoveredDevices.set(deviceId, freshPeripheral);

// Connect to fresh peripheral
await new Promise<void>((resolve, reject) => {
freshPeripheral.connect((error: string) => {
if (error) {
reject(new Error(`Fresh peripheral connection failed: ${error}`));
} else {
connectedDevices.set(deviceId, freshPeripheral);
resolve();
}
});
});

// Attempt service discovery with fresh peripheral (single attempt)
logger?.info('[NobleBLE] Attempting service discovery with fresh peripheral');
await wait(1000); // Give fresh connection more time to stabilize

return await discoverServicesAndCharacteristics(freshPeripheral);
} catch (error) {
logger?.error('[NobleBLE] Fresh scan and connection failed:', error);
throw error;
}
}

// Enhanced service discovery with p-retry for robust BLE connection
async function discoverServicesAndCharacteristicsWithRetry(
peripheral: Peripheral,
deviceId: string
): Promise<CharacteristicPair> {
return pRetry(
async attemptNumber => {
logger?.info('[NobleBLE] Starting service discovery:', {
deviceId,
peripheralState: peripheral.state,
attempt: attemptNumber,
maxRetries: 5,
targetUUIDs: ONEKEY_SERVICE_UUIDS,
});

// Strategy: Force reconnect on 3rd attempt to clear potential state issues
if (attemptNumber === 3) {
logger?.info('[NobleBLE] Attempting force reconnect to clear connection state...');
try {
await forceReconnectPeripheral(peripheral, deviceId);
} catch (error) {
logger?.error('[NobleBLE] Force reconnect failed:', error);
throw error;
}
}

// Progressive delay strategy - handled by p-retry, but add extra wait for higher attempts
if (attemptNumber > 1) {
logger?.info(`[NobleBLE] Service discovery retry attempt ${attemptNumber}/5`);
}

// Verify connection state before attempting service discovery
if (peripheral.state !== 'connected') {
throw new Error(`Device not connected: ${peripheral.state}`);
}

try {
return await discoverServicesAndCharacteristics(peripheral);
} catch (error) {
logger?.error(`[NobleBLE] No services found (attempt ${attemptNumber}/5)`);

if (attemptNumber < 5) {
logger?.error(`[NobleBLE] Will retry service discovery (attempt ${attemptNumber + 1}/5)`);
}

throw error; // p-retry will handle the retry logic
}
},
{
retries: 4, // Total 5 attempts (initial + 4 retries)
factor: 1.5, // Exponential backoff: 1000ms → 1500ms → 2250ms → 3000ms
minTimeout: 1000, // Start with 1 second delay
maxTimeout: 3000, // Maximum 3 seconds delay
onFailedAttempt: error => {
// This runs after each failed attempt
logger?.error(`[NobleBLE] Service discovery attempt ${error.attemptNumber} failed:`, {
message: error.message,
retriesLeft: error.retriesLeft,
nextRetryIn: `${Math.min(1000 * 1.5 ** error.attemptNumber, 3000)}ms`,
});
},
}
);
}

// Connect to device - supports both discovered and direct connection modes
async function connectDevice(deviceId: string, webContents: WebContents): Promise<void> {
logger?.info('[NobleBLE] Connect device request:', {
Expand Down Expand Up @@ -746,14 +909,17 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
// Continue to re-setup the connection properly
}

// Discover services and characteristics
// Discover services and characteristics with enhanced retry including fresh scan
try {
const characteristics = await discoverServicesAndCharacteristics(peripheral);
const characteristics = await connectAndDiscoverWithFreshScan(deviceId);
deviceCharacteristics.set(deviceId, characteristics);
logger?.info('[NobleBLE] Device ready for communication:', deviceId);
return;
} catch (error) {
logger?.error('[NobleBLE] Service/characteristic discovery failed:', error);
logger?.error(
'[NobleBLE] Service/characteristic discovery failed after all attempts:',
error
);
throw error;
}
}
Expand All @@ -780,15 +946,18 @@ async function connectDevice(deviceId: string, webContents: WebContents): Promis
// Set up unified disconnect listener
setupDisconnectListener(connectedPeripheral, deviceId, webContents);

// Discover services and characteristics
discoverServicesAndCharacteristics(connectedPeripheral)
// Discover services and characteristics with enhanced retry including fresh scan
connectAndDiscoverWithFreshScan(deviceId)
.then(characteristics => {
deviceCharacteristics.set(deviceId, characteristics);
logger?.info('[NobleBLE] Device ready for communication:', deviceId);
resolve();
})
.catch(error => {
logger?.error('[NobleBLE] Service/characteristic discovery failed:', error);
logger?.error(
'[NobleBLE] Service/characteristic discovery failed after all attempts:',
error
);
// Disconnect on failure
connectedPeripheral.disconnect();
reject(error);
Expand Down Expand Up @@ -1053,6 +1222,7 @@ async function unsubscribeNotifications(deviceId: string): Promise<void> {
// Setup IPC handlers
export function setupNobleBleHandlers(webContents: WebContents): void {
try {
console.log('NOBLE_VERSION_771');
// @ts-ignore – electron-log is only available at runtime
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
logger = require('electron-log') as Logger;
Expand Down
Loading