Skip to content

Conversation

wabicai
Copy link
Contributor

@wabicai wabicai commented Aug 16, 2025

  • 使用@stoprocent/noble package
  • 添加 pairing流程,防止在windows端订阅的时候,进入“假连接”状态(3秒一次,总共十次,对应在pro设备中30秒会自动连接超时)
  • 每次通讯都进行取消订阅相关操作,防止内存泄露
  • 对于正常连接设备,会多一次 pairing 初始化通讯流程
  • 修复example 的蓝牙连接流程
  • 添加check-versions 脚本,预防package.json 版本 错误

Summary by CodeRabbit

  • New Features

    • Desktop example builds available for macOS and Windows.
  • Improvements

    • Significantly improved BLE reliability on Windows (pairing handling, retries, cleanup).
    • Auto-refresh of BLE subscriptions for more stable data streaming.
    • Expo example: clearer firmware update flow and warnings when features are unavailable.
    • Electron/Web build robustness (publicPath fixes, larger build memory).
  • Bug Fixes

    • Device auto-select now avoids desktop-web-ble conflicts; shows disconnected notice when applicable.
  • Chores

    • Version bump to 1.1.10 across packages.
    • Added pre-publish version checks and updated CI workflows for platform-specific builds.

Copy link
Contributor

coderabbitai bot commented Aug 16, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

The PR finalizes 1.1.10 release versions, replaces Abandonware Noble with @stoprocent/noble across Electron, adds @onekeyfe/hd-transport-electron, introduces BLE write/subscribe robustness (Windows pairing retries, unified cleanup, soft refresh), updates Electron build configs/scripts, adjusts Expo examples, and refactors CI to OS-specific desktop builds and version-gated publish.

Changes

Cohort / File(s) Summary
Electron Example App Config
packages/connect-examples/electron-example/package.json, packages/connect-examples/electron-example/electron-builder.config.js, packages/connect-examples/electron-example/webpack.config.ts, packages/connect-examples/electron-example/src/index.ts
Bump to 1.1.10; replace Abandonware Noble with @stoprocent/noble and add @onekeyfe/hd-transport-electron; add OS-specific make scripts and deps rebuild; configure asarUnpack for native modules; update externals; move BLE init to app ready.
Electron Transport BLE Core
packages/hd-transport-electron/package.json, packages/hd-transport-electron/src/noble-ble-handler.ts, packages/hd-transport-electron/src/ble-ops.ts, packages/hd-transport-electron/src/types/noble-extended.ts
Bump to 1.1.10; add @onekeyfe/hd-core/hd-transport; switch to @stoprocent/noble; add p-retry; implement Windows pairing retries, unified device cleanup, subscription state machine, softRefreshSubscription, listener cleanup, and expanded diagnostics. Types updated to new noble.
Expo Example Adjustments
packages/connect-examples/expo-example/package.json, packages/connect-examples/expo-example/webpack.config.js, packages/connect-examples/expo-example/src/components/DeviceList.tsx, packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx, packages/connect-examples/expo-example/src/components/BaseTestRunner/useRunnerTest.ts
Bump deps to 1.1.10; increase build memory; webpack tweaks for Electron mode, SPA fallback, shims, defines; refine auto-select behavior for desktop-web-ble; firmware screen flow/logging updates; expose preCheckResponse hook in test runner.
Version Alignments (Packages)
packages/core/package.json, packages/hd-ble-sdk/package.json, packages/hd-common-connect-sdk/package.json, packages/hd-transport-emulator/package.json, packages/hd-transport-http/package.json, packages/hd-transport-lowlevel/package.json, packages/hd-transport-react-native/package.json, packages/hd-transport-web-device/package.json, packages/hd-transport/package.json, packages/hd-web-sdk/package.json, packages/shared/package.json
Bump versions from 1.1.10-alpha.x to 1.1.10; update internal dependencies to 1.1.10; minor description updates where present.
Root Scripts and Tooling
package.json, .husky/pre-commit, scripts/check-versions.js
Add version consistency check and gate publish; split desktop build scripts per OS; add dev script and electron-builder devDep; extend resolutions; run check-versions in pre-commit; add new version-check script.
Shared Constants
packages/shared/src/constants.ts
Add new IPC key: NOBLE_BLE_CANCEL_PAIRING.
CI/CD Workflows
.github/workflows/package-publish.yml, .github/workflows/build-example-desktop.yml, .github/workflows/build-example-desktop-mac.yml, .github/workflows/build-example-desktop-win.yml
Publish flow now detects prerelease vs stable and sets dist-tag; adds get-version job; split desktop build into reusable mac/win workflows; delegate from umbrella workflow; set permissions and artifact uploads per OS.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App as Electron App
  participant Main as Main Process
  participant Web as BrowserWindow WebContents
  participant BLE as Noble (@stoprocent/noble)
  participant Dev as BLE Device

  App->>Main: app.on('ready')
  Main->>Web: createMainWindow()
  Main->>Main: setupNobleBleHandlers(Web)
  Main->>BLE: noble.startScanning()
  BLE-->>Main: discover(peripheral)
  Main->>BLE: connect(peripheral)
  BLE-->>Main: connected
  Main->>BLE: discoverServices/Characteristics
  Main->>BLE: subscribe(notify)
  BLE-->>Main: data(chunk)
  Main->>Main: processNotificationData()
  alt complete packet
    Main-->>Web: send packet to app layer
  else incomplete/error
    Main->>Main: accumulate/log
  end
Loading
sequenceDiagram
  autonumber
  participant Main as Main Process
  participant BLE as Noble
  participant Dev as BLE Device
  participant Retry as p-retry
  participant IPC as IPC

  rect rgba(200,230,255,0.3)
  note over Main: Windows-specific write flow
  Main->>Retry: attemptWindowsWriteUntilPaired()
  loop backoff retries
    Retry->>BLE: writeCharacteristicWithAck()
    alt abortable error (e.g., pairing lost)
      Retry-->>Main: throw Abort
      Main->>Main: cleanupDevice(deviceId, deep)
      Main->>BLE: stopNotifications/unsubscribe
      Main->>Main: softRefreshSubscription()
    else success
      Retry-->>Main: ok
    end
  end
  end

  rect rgba(230,200,255,0.25)
  note over IPC,Main: Cancel pairing
  IPC-->>Main: NOBLE_BLE_CANCEL_PAIRING
  Main->>Main: cleanup all devices, stop scanning
  Main->>BLE: remove listeners
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8e3b513 and 3ef8c4e.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (33)
  • .github/workflows/build-example-desktop-mac.yml (1 hunks)
  • .github/workflows/build-example-desktop-win.yml (1 hunks)
  • .github/workflows/build-example-desktop.yml (1 hunks)
  • .github/workflows/package-publish.yml (2 hunks)
  • .husky/pre-commit (1 hunks)
  • package.json (4 hunks)
  • packages/connect-examples/electron-example/electron-builder.config.js (1 hunks)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/electron-example/src/index.ts (1 hunks)
  • packages/connect-examples/electron-example/webpack.config.ts (1 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/src/components/BaseTestRunner/useRunnerTest.ts (1 hunks)
  • packages/connect-examples/expo-example/src/components/DeviceList.tsx (2 hunks)
  • packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx (3 hunks)
  • packages/connect-examples/expo-example/webpack.config.js (1 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/ble-ops.ts (1 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (29 hunks)
  • packages/hd-transport-electron/src/types/noble-extended.ts (1 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
  • packages/shared/src/constants.ts (1 hunks)
  • scripts/check-versions.js (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/custom-binding-ble

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or Summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@revan-zhang
Copy link
Contributor

revan-zhang commented Aug 16, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

license/snyk check is complete. No issues have been found. (View Details)

@wabicai wabicai changed the title fix: use stoprocent ble fix: use @stoprocent/noble ble Aug 16, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🔭 Outside diff range comments (9)
packages/hd-transport-electron/src/noble-ble-handler.ts (9)

26-26: Fix typo in comment

“bundlinpissues” → “bundling issues”

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

75-81: Connection timeout mismatch (comment vs value)

Comment says 15 seconds, constant is 3000 ms (3 seconds). This can cause spurious timeouts on BLE connect. Align the value with the comment, or update the comment.

-const CONNECTION_TIMEOUT = 3000; // 15 seconds for device connection
+const CONNECTION_TIMEOUT = 15000; // 15 seconds for device connection

388-389: Syntax error: stray “\n” literals and missing deviceCache

This line contains literal “\n” sequences in source. That will not compile. It also references deviceCache, which is not defined anywhere.

Option A (quick fix: remove the broken caching block):

-  // First check if we have a recent cached peripheral\n  const cachedDevice = deviceCache.get(targetDeviceId);\n  if (cachedDevice && (Date.now() - cachedDevice.lastSeen) < 30000) { // 30 seconds cache\n    logger?.info('[NobleBLE] Using cached device for fast connection:', targetDeviceId);\n    \n    // Use cached device if it was successful before\n    if (cachedDevice.connectionSuccess) {\n      discoveredDevices.set(targetDeviceId, cachedDevice.peripheral);\n      return cachedDevice.peripheral;\n    }\n  }\n\n  logger?.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
+  logger?.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);

Option B (implement a proper cache): I can provide a full patch introducing a typed deviceCache and wiring updates on discovery and successful connections.

Do you want me to wire up a proper cache implementation?


390-446: Leak: on('discover') listener not removed on timeout

When the targeted scan times out, you stop scanning but never remove the onDiscover handler, which can leak listeners across scans.

     const timeout = setTimeout(() => {
       if (noble) {
         noble.stopScanning();
+        noble.removeListener('discover', onDiscover);
       }
       logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
       resolve(null);
     }, FAST_SCAN_TIMEOUT);

431-444: Callback error handling: handle both string and Error

Noble historically calls callbacks with string errors. Using error.message can produce undefined. Normalize error values before wrapping in TypedError.

Introduce a helper once and use it everywhere:

function toErrorMessage(err: unknown): string {
  if (typeof err === 'string') return err;
  if (err && typeof err === 'object' && 'message' in err) {
    // eslint-disable-next-line @typescript-eslint/no-base-to-string
    return (err as any).message ?? String(err);
  }
  try { return JSON.stringify(err); } catch { return String(err); }
}

Then:

- reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+ reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, toErrorMessage(error)));

Apply the same pattern to all callbacks in this file that use error.message.


481-486: Callback error handling: handle both string and Error

Same issue as above. Wrap the error with a normalizer before building a TypedError.

- reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+ reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, toErrorMessage(error)));

734-737: Disconnect listener missing for fresh peripheral path

When connectAndDiscoverWithFreshScan connects a freshPeripheral, you don’t register a disconnect listener here (this function doesn’t have webContents). If discovery falls back to this path, disconnect events may not be observed.

Pass webContents into connectAndDiscoverWithFreshScan and hook the listener:

-async function connectAndDiscoverWithFreshScan(deviceId: string): Promise<CharacteristicPair> {
+async function connectAndDiscoverWithFreshScan(deviceId: string, webContents: WebContents): Promise<CharacteristicPair> {
   ...
-  await new Promise<void>((resolve, reject) => {
+  await new Promise<void>((resolve, reject) => {
     freshPeripheral.connect((error: Error | undefined) => {
       ...
       connectedDevices.set(deviceId, freshPeripheral);
+      setupDisconnectListener(freshPeripheral, deviceId, webContents);
       resolve();
     });
   });

Update the two call sites in connectDevice accordingly.


872-885: Potential recursion loop on subscription operations

connectDevice recurses if a subscription operation is in-flight. If that state gets stuck, you can recurse indefinitely. Consider a max retry count or turning this into a loop with a backoff.


1226-1230: Leftover debug log

console.log('NOBLE_VERSION_771') looks like a build marker. Gate it behind a dev flag or remove.

-    console.log('NOBLE_VERSION_771');
+    if (process.env.NODE_ENV !== 'production') console.log('NOBLE_VERSION_771');
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 23490bb and 1bba138.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • packages/connect-examples/electron-example/package.json (1 hunks)
  • packages/connect-examples/electron-example/webpack.config.ts (1 hunks)
  • packages/hd-transport-electron/package.json (1 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (11 hunks)
  • packages/hd-transport-electron/src/types/noble-extended.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/hd-transport-electron/src/noble-ble-handler.ts (2)
packages/hd-transport-electron/src/types/noble-extended.ts (1)
  • NobleModule (22-34)
packages/shared/src/HardwareError.ts (1)
  • HardwareErrorCode (77-459)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
packages/hd-transport-electron/package.json (1)

28-30: Match runtime deps with bundling/externals

Good to see @stoprocent/noble added here. Ensure your bundling/externals setup for packages consuming this code doesn’t strip the native dependency tree that noble needs at runtime (notably the HCI socket). Otherwise, ship them explicitly in this package.json.

packages/hd-transport-electron/src/noble-ble-handler.ts (1)

96-157: Packet assembly: verify header offsets

You read payload length at offset 5 (readInt32BE(5)) and subtract COMMON_HEADER_SIZE from current buffer length when checking completeness. Confirm this matches the exact header layout emitted by @onekeyfe/hd-transport, otherwise you can over/under-assemble.

If needed, I can add assertions or structured parsing to validate header fields before assembling.

@wabicai wabicai marked this pull request as draft August 18, 2025 12:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (1)
packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx (1)

568-591: Map SDK platform to the correct IPlatform value at runtime

Hardcoding 'web' conflicts with the SDK’s union of native | desktop | ext | web | web-embed. Use Platform.OS to derive one of these.

• packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx (around lines 568–580)
• packages/connect-examples/expo-example/src/data/device.ts (around lines 145, 152)

Suggested change:

+ import { Platform } from 'react-native';
+ import type { IPlatform } from 'core/src/types/api/firmwareUpdate';

   if (type === 'ble' || type === 'firmware') {
     setShowUpdateDialog(true);
-    console.log('Starting firmware update:', { type, deviceId: selectDevice.connectId, platform: 'web' });
+    // Map RN platform to SDK’s IPlatform
+    const sdkPlatform: IPlatform = Platform.OS === 'web'
+      ? 'web'
+      : ['macos', 'windows'].includes(Platform.OS)
+      ? 'desktop'
+      : 'native';
+    console.log('Starting firmware update:', {
+      type,
+      deviceId: selectDevice.connectId,
+      platform: sdkPlatform,
+    });

     const deviceId = selectDevice.connectId;
     console.log('Using device ID for firmware update:', deviceId);

-    const res = await sdk.firmwareUpdateV2(deviceId, {
-      binary: fileData,
-      updateType: type,
-      platform: 'web',
-    });
+    const res = await sdk.firmwareUpdateV2(deviceId, {
+      binary: fileData,
+      updateType: type,
+      platform: sdkPlatform,
+    });

Also replace other hardcoded platform: 'web' entries in src/data/device.ts with the same runtime mapping.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1bba138 and 6b88779.

📒 Files selected for processing (3)
  • package.json (1 hunks)
  • packages/connect-examples/expo-example/src/components/DeviceList.tsx (2 hunks)
  • packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx (2)
packages/core/src/utils/logger.ts (1)
  • error (49-56)
packages/connect-examples/expo-example/src/views/FirmwareScreen/MessageBox.tsx (1)
  • MessageBox (7-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
package.json (1)

52-52: Dev script verified—approving change
The packages/hd-transport-electron/package.json defines a dev script, so yarn dev:transport-electron will run as expected.

packages/connect-examples/expo-example/src/components/DeviceList.tsx (1)

132-141: Good: no auto-select for desktop BLE to prevent surprise connects.

This change reduces unintended connections and device switching issues. One note: the comment above says WebUSB auth is handled in the SDK, but there’s still a manual navigator.usb.requestDevice call earlier. Either remove that call or update the comment to avoid confusion and double prompts.

Outside this hunk, consider removing the manual WebUSB request:

// Remove this block if SDK handles permission internally:
if (connectionType === 'webusb') {
  try {
    await window?.navigator?.usb?.requestDevice({ filters: ONEKEY_WEBUSB_FILTER });
  } catch (error) {
    console.warn('WebUSB request device failed:', error);
  }
}

@wabicai wabicai marked this pull request as ready for review August 21, 2025 09:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/hd-transport-electron/src/noble-ble-handler.ts (2)

414-424: Targeted scan: remove 'discover' listener on timeout to prevent leaks.

When the timeout fires you stop scanning and resolve(null) but do not remove the onDiscover listener. This can leak handlers across calls.

     const timeout = setTimeout(() => {
       if (noble) {
         noble.stopScanning();
       }
       logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
-      resolve(null);
+      if (noble) {
+        noble.removeListener('discover', onDiscover);
+      }
+      resolve(null);
     }, FAST_SCAN_TIMEOUT);

898-911: Avoid recursive reconnect loop.

If operation state stays non-idle, return connectDevice(...) recursively chains and can grow stack on repeated retries. Use a loop/poll with a bounded wait instead.

-        await wait(100);
-        // Retry connection after waiting
-        return connectDevice(deviceId, webContents);
+        for (let i = 0; i < 50; i++) { // 5s max
+          const s = subscriptionOperations.get(deviceId);
+          if (!s || s === 'idle') break;
+          await wait(100);
+        }
+        // fall through and continue in the current call
♻️ Duplicate comments (13)
packages/connect-examples/electron-example/package.json (1)

23-30: Add @stoprocent/bluetooth-hci-socket to avoid Linux runtime failures

Noble needs the HCI socket on Linux. Optional deps often don’t make it into packaged Electron apps. Add it explicitly to prevent “module not found” at runtime.

   "dependencies": {
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",
     "electron-is-dev": "^3.0.1",
#!/bin/bash
set -euo pipefail
cd packages/connect-examples/electron-example
# Confirm dependency presence and version
npm ls @stoprocent/bluetooth-hci-socket || true
# Sanity check externals and imports
rg -n "@stoprocent/bluetooth-hci-socket" -g "!**/dist/**" -C2 || true
packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx (3)

410-426: Race-proof loadOnekeyFeatures by capturing connectId

If connectId changes mid-flight, you can set stale state later. Capture it and use the captured value during the call.

-  const loadOnekeyFeatures = useCallback(async () => {
-    if (!sdk || !selectDevice?.connectId) return undefined;
+  const loadOnekeyFeatures = useCallback(async () => {
+    if (!sdk || !selectDevice?.connectId) return undefined;
+    const currentId = selectDevice.connectId;
     try {
       console.log('loadOnekeyFeatures: Starting to load OneKey features...');
-      const res = await sdk.getOnekeyFeatures(selectDevice.connectId);
+      const res = await sdk.getOnekeyFeatures(currentId);
       console.log('loadOnekeyFeatures: Result:', res);
       if (res.success) {
         return res.payload;
       }
       return undefined;
     } catch (error) {
       console.error('loadOnekeyFeatures: Error:', error);
       return undefined;
     }
   }, [sdk, selectDevice?.connectId]);

430-468: Avoid setState after unmount/selection change; normalize errors

Add a cancel flag and capture connectId. Check both before setting state. Also guard error extraction.

   useEffect(() => {
     if (!sdk) return;
     if (selectDevice?.connectId == null) {
       setFeatures(undefined);
       setOnekeyFeatures(undefined);
       return;
     }
-    const loadDeviceFeatures = async () => {
-      setConnecting(true);
+    let cancelled = false;
+    const currentId = selectDevice.connectId;
+
+    const loadDeviceFeatures = async () => {
+      if (cancelled) return;
+      setConnecting(true);
       setFeatures(undefined);
       setOnekeyFeatures(undefined);
       setError(undefined);
       try {
-        console.log('Loading device features for:', selectDevice.connectId);
-        const featuresRes = await sdk.getFeatures(selectDevice.connectId);
+        console.log('Loading device features for:', currentId);
+        const featuresRes = await sdk.getFeatures(currentId);
         console.log('getFeatures result:', featuresRes);
-        if (featuresRes.success) {
+        if (featuresRes.success) {
           const fetchedFeatures = featuresRes.payload;
           console.log('Features loaded successfully, now loading OneKey features...');
           const fetchedOnekeyFeatures = await loadOnekeyFeatures();
-          setFeatures(fetchedFeatures);
-          setOnekeyFeatures(fetchedOnekeyFeatures);
+          if (!cancelled && currentId === selectDevice?.connectId) {
+            setFeatures(fetchedFeatures);
+            setOnekeyFeatures(fetchedOnekeyFeatures);
+          }
         } else {
-          console.error('Failed to get features:', featuresRes.payload.error);
-          setError(featuresRes.payload.error);
+          const msg =
+            (featuresRes as any)?.payload?.error ??
+            String((featuresRes as any)?.payload ?? 'Unknown error');
+          console.error('Failed to get features:', msg);
+          if (!cancelled) setError(msg);
         }
       } catch (error) {
         console.error('Exception in loadDeviceFeatures:', error);
-        setError(error instanceof Error ? error.message : String(error));
+        if (!cancelled)
+          setError(error instanceof Error ? error.message : String(error));
       } finally {
-        setConnecting(false);
+        if (!cancelled) setConnecting(false);
       }
     };
 
     loadDeviceFeatures();
+    return () => {
+      cancelled = true;
+    };
   }, [sdk, selectDevice?.connectId, loadOnekeyFeatures]);

669-675: Localize messages and gate the disconnected notice by mode; add a refresh

Avoid hard-coded strings. Show the disconnected tip only for desktop-web-ble. Offer a one-click refresh for OneKey Features.

-          {features && !onekeyFeatures && (
-            <MessageBox message="OneKey Features not available. Try clicking 'Refresh OneKey Features' button." />
-          )}
-          {selectDevice && selectDevice.state === 'disconnected' && (
-            <MessageBox message={`Device "${selectDevice.name}" shows as disconnected. In desktop-web-ble mode, this is normal - the device can still communicate via Bluetooth.`} />
-          )}
+          {features && !onekeyFeatures && (
+            <Stack padding="$2" backgroundColor="$bgCaution" alignItems="center" gap="$2">
+              <Text>
+                {intl.formatMessage({ id: 'tip__onekey_features_unavailable' })}
+              </Text>
+              <Button size="small" onPress={loadOnekeyFeatures}>
+                {intl.formatMessage({ id: 'label__refresh_onekey_features' })}
+              </Button>
+            </Stack>
+          )}
+          {selectDevice &&
+            selectDevice.state === 'disconnected' &&
+            connectionType === 'desktop-web-ble' && (
+              <MessageBox
+                message={intl.formatMessage(
+                  { id: 'tip__desktop_web_ble_disconnected_notice' },
+                  { name: selectDevice.name }
+                )}
+              />
+            )}

Add the supporting imports and wiring outside this hunk:

// imports (top of file)
import { connectionTypeAtom } from '../../atoms/deviceConnectAtoms';

// inside FirmwareUpdate component
const connectionType = useAtomValue(connectionTypeAtom);

Remember to add i18n keys:

  • tip__onekey_features_unavailable
  • label__refresh_onekey_features
  • tip__desktop_web_ble_disconnected_notice
packages/hd-transport-electron/src/noble-ble-handler.ts (9)

1269-1285: Unsubscribe error: normalize and keep state consistent.

Don’t assume Error; also ensure state resets even if logging throws.

-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

Optionally: use toErrorMessage(error) in a structured log field.


21-21: Importing types from @stoprocent/noble can fail (no shipped d.ts).

Prefer importing types from 'noble' or add a local shim that re-exports them. Runtime require can still use '@stoprocent/noble'.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

Alternatively, add a local declaration:

// types/@stoprocent__noble.d.ts
declare module '@stoprocent/noble' {
  export * from 'noble';
}

598-602: Service discovery error: normalize error type.

Don’t assume Error; Noble may pass strings. Use unknown and a normalizer.

-    peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error: Error | undefined, services: Service[]) => {
+    peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error: unknown, services: Service[]) => {
       if (error) {
         logger?.error('[NobleBLE] Service discovery failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
         return;
       }

Also add at top:

-import { safeLog } from './types/noble-extended';
+import { safeLog, toErrorMessage } from './types/noble-extended';

616-620: Characteristic discovery error: same normalization.

-        (error: Error | undefined, characteristics: Characteristic[]) => {
+        (error: unknown, characteristics: Characteristic[]) => {
           if (error) {
             logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-            reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+            reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
             return;
           }

698-702: Force reconnect error: avoid .message on non-Error.

-      if (error) {
+      if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));
       } else {

748-751: Fresh peripheral connect: normalize error.

-        if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+        if (error) {
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));

960-966: Connection error: normalize error before constructing TypedError.

-      if (error) {
+      if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }

1102-1106: Chunk write error: normalize before TypedError.

-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
           logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           return;
         }

1201-1223: Rebuild subscription: normalize subscribe error and avoid silent failures.

-  async function rebuildAppSubscription(deviceId: string, notifyCharacteristic: Characteristic): Promise<void> {
+  async function rebuildAppSubscription(deviceId: string, notifyCharacteristic: Characteristic): Promise<void> {
     notifyCharacteristic.removeAllListeners('data');
     await new Promise<void>(resolve => notifyCharacteristic.unsubscribe(() => resolve()));
     await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) return reject(error);
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
         resolve();
       });
     });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6b88779 and 3d25d09.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (7)
  • package.json (2 hunks)
  • packages/connect-examples/electron-example/package.json (1 hunks)
  • packages/connect-examples/electron-example/webpack.config.ts (1 hunks)
  • packages/connect-examples/expo-example/src/views/FirmwareScreen/index.tsx (3 hunks)
  • packages/hd-transport-electron/package.json (1 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (19 hunks)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
packages/connect-examples/electron-example/webpack.config.ts (1)

51-52: Externals update verified and approved

I ran a search for any lingering @abandonware/ references and found none. Swapping to @stoprocent/* externals is correct and ensures native modules won’t be bundled into your main Electron build. Everything looks good—no further action needed.

package.json (2)

52-52: Handy script addition

Adding dev:transport-electron improves DX for the Electron transport. No issues.


63-66: Resolutions confirmed—no duplicate versions detected

We ran yarn list --pattern for each pinned package. Each dependency appears exactly once:

All resolutions are effective and deduped. No further action needed.

packages/hd-transport-electron/src/utils/blePairing.ts (1)

43-51: Overall design LGTM.

Nice, tight probe: unsub→sub→once('data')→write, bounded by cycles and interval, with careful listener cleanup. The logs will help debug Windows “false connection” states.

Also applies to: 76-88, 92-102

packages/hd-transport-electron/src/noble-ble-handler.ts (2)

265-266: Runtime noble import looks good.

Dynamic require of '@stoprocent/noble' at runtime is correct and pairs well with type-only imports. Nice.


92-92: Align connection timeout with device pairing window.

You probe pairing for up to ~30s elsewhere. A 15s connection timeout may abort too early on Windows. Consider 30000 ms, or make it configurable.

-const CONNECTION_TIMEOUT = 15000; // 15 seconds for device connection
+const CONNECTION_TIMEOUT = 30000; // 30 seconds; align with device pairing window

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
packages/hd-transport-electron/src/noble-ble-handler.ts (6)

416-471: Leak/race: remove 'discover' listener on timeout too

If the targeted scan times out, the 'discover' listener stays attached. That can leak and fire later unexpectedly. Define the handler first so you can remove it in all paths.

-  return new Promise((resolve, reject) => {
-    const timeout = setTimeout(() => {
-      if (noble) {
-        noble.stopScanning();
-      }
-      logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
-      resolve(null);
-    }, FAST_SCAN_TIMEOUT);
-
-    // Set up discovery handler for target device
-    const onDiscover = (peripheral: Peripheral) => {
+  return new Promise((resolve, reject) => {
+    // Define first so we can remove in all branches
+    const onDiscover = (peripheral: Peripheral) => {
       if (peripheral.id === targetDeviceId) {
-        clearTimeout(timeout);
+        clearTimeout(timeout);
         if (noble) {
           noble.stopScanning();
         }
         // Cache the found device
         discoveredDevices.set(peripheral.id, peripheral);
         logger?.info('[NobleBLE] Target device found:', {
           id: peripheral.id,
           name: peripheral.advertisement?.localName || 'Unknown',
         });
         // Clean up listener
         if (noble) {
           noble.removeListener('discover', onDiscover);
         }
         resolve(peripheral);
       }
     };
 
+    const timeout = setTimeout(() => {
+      if (noble) {
+        noble.stopScanning();
+        noble.removeListener('discover', onDiscover);
+      }
+      logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
+      resolve(null);
+    }, FAST_SCAN_TIMEOUT);
+
     // Add discovery listener
     if (noble) {
       noble.on('discover', onDiscover);
     }

457-465: Start-scanning callback: type error as unknown and normalize message

Avoid assuming Error. Use toErrorMessage for stable strings.

-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: unknown) => {
         if (error) {
           clearTimeout(timeout);
           if (noble) {
             noble.removeListener('discover', onDiscover);
           }
           logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, toErrorMessage(error)));
           return;
         }

899-910: Avoid recursive reconnect wait; use bounded loop

Recursive self-call can grow the stack and complicate flow. Wait in a loop, then continue.

-        // Wait for ongoing operation to complete
-        await wait(100);
-        // Retry connection after waiting
-        return connectDevice(deviceId, webContents);
+        // Wait for ongoing operation to complete with a bounded loop
+        for (let i = 0; i < 50; i++) { // ~5s max
+          const s = subscriptionOperations.get(deviceId);
+          if (!s || s === 'idle') break;
+          await wait(100);
+        }
+        // Continue below

954-957: On connection timeout, attempt to abort the in-flight connect

Rejecting alone can leave a dangling OS connect. Attempt a disconnect to reset state.

-    const timeout = setTimeout(() => {
-      reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'Connection timeout'));
-    }, CONNECTION_TIMEOUT);
+    const timeout = setTimeout(() => {
+      try {
+        connectedPeripheral.disconnect(); // best-effort abort
+      } catch (_) { /* ignore */ }
+      reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, 'Connection timeout'));
+    }, CONNECTION_TIMEOUT);

1272-1288: Unsubscribe callback: normalize error and keep types wide

Log a clean message; don’t assume Error.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', toErrorMessage(error));
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

1295-1295: Remove stray console.log

Looks like a leftover debug marker.

-    console.log('NOBLE_VERSION_771');
+    // console.log removed (debug)
♻️ Duplicate comments (11)
packages/hd-transport-electron/src/noble-ble-handler.ts (11)

598-602: Service discovery: error typing and normalization

Type the error as unknown and normalize before using. Matches earlier guidance.

-    peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error: Error | undefined, services: Service[]) => {
+    peripheral.discoverServices(ONEKEY_SERVICE_UUIDS, (error: unknown, services: Service[]) => {
       if (error) {
         logger?.error('[NobleBLE] Service discovery failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
         return;
       }

616-620: Characteristic discovery: error typing and normalization

Same fix here.

-        (error: Error | undefined, characteristics: Characteristic[]) => {
+        (error: unknown, characteristics: Characteristic[]) => {
           if (error) {
             logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-            reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+            reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
             return;
           }

698-702: Normalize force reconnect errors

Avoid error.message on non-Error values.

-    peripheral.connect((error: Error | undefined) => {
+    peripheral.connect((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));
       } else {

748-751: Normalize fresh connect errors

Same normalization when connecting fresh peripheral.

-      freshPeripheral.connect((error: Error | undefined) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));
         } else {

960-966: Normalize connection errors

Don’t assume Error.

-    connectedPeripheral.connect((error: Error | undefined) => {
+    connectedPeripheral.connect((error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }

1084-1090: Chunk logging: don’t dump full payloads

Full hex dumps can leak data and bloat logs. Log preview and sizes only.

-  logger?.info('[NobleBLE] Splitting into chunks:', {
+  logger?.info('[NobleBLE] Splitting into chunks:', {
     deviceId,
     totalChunks: chunks.length,
     chunkSize: BLE_PACKET_SIZE,
     totalDataLength: buffer.length,
   });

And below (per-chunk):

-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
-        withResponse: false,
+        chunkPreview: `${preview}...`,
+        withResponse: true,
       });

1102-1106: Chunk write: normalize errors

Don’t use error.message blindly.

-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
           logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           return;
         }

1240-1252: Don’t use async inside new Promise; use try/finally

This also guarantees operation state reset on both paths.

-  return new Promise(async (resolve, reject) => {
-    try {
-      await pairingProbeAndRebuild(deviceId);
-      subscriptionOperations.set(deviceId, 'idle');
-      resolve();
-    } catch (e) {
-      logger?.error('[NobleBLE] Pairing probe failed, forcing disconnect to reset state', { deviceId, error: e });
-      // Force disconnect and cleanup on pairing failure to prevent zombie state
-      await disconnectDevice(deviceId);
-      subscriptionOperations.set(deviceId, 'idle');
-      reject(e);
-    }
-  });
+  try {
+    await pairingProbeAndRebuild(deviceId);
+  } catch (e) {
+    logger?.error('[NobleBLE] Pairing probe failed, forcing disconnect to reset state', { deviceId, error: e });
+    await disconnectDevice(deviceId);
+    throw e;
+  } finally {
+    subscriptionOperations.set(deviceId, 'idle');
+  }

1354-1355: IPC write log: report byte count, not hex chars

Divide by 2 to get bytes.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

21-21: Fix type-only imports: prefer 'noble' types; '@stoprocent/noble' may lack d.ts

Keep the runtime require from '@stoprocent/noble', but import types from 'noble' to avoid missing types at build time.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

If you want to keep the import from '@stoprocent/noble', add a local .d.ts shim that re-exports types from 'noble'.


1047-1065: Single write: use with-response (safer), and normalize errors

With-response detects link issues earlier. Also avoid calling isPairingError on unknown.

-    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length });
+    const withoutResponse = false; // safer for control packets
+    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length, withoutResponse });
@@
-    return new Promise((resolve, reject) => {
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+    return new Promise((resolve, reject) => {
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
           logger?.error('[NobleBLE] Write single failed:', error);
-          if (isPairingError(error)) {
+          if (isPairingError(error)) {
             reject(ERRORS.TypedError(
               HardwareErrorCode.BleWriteCharacteristicError,
-              `Pairing required for write operation: ${error.message}`
+              `Pairing required for write operation: ${toErrorMessage(error)}`
             ));
           } else {
-            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           }
           return;
         }
 
-        logger?.info('[NobleBLE] Write single ok', { deviceId });
+        logger?.info('[NobleBLE] Write single ok', { deviceId });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3d25d09 and 8d719d9.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (19 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/hd-transport-electron/src/noble-ble-handler.ts (1)

91-91: Revisit CONNECTION_TIMEOUT (align with Pro’s 30s auto-timeout or make it configurable)

The PR text mentions a 30-second pairing/timeout window on Pro. Using 15s here may cause premature failures. Consider 30s or a config/env-based timeout.

-const CONNECTION_TIMEOUT = 15000; // 15 seconds for device connection
+const CONNECTION_TIMEOUT = 30000; // 30 seconds for device connection (align with device behavior)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
packages/hd-transport/package.json (1)

10-12: Add an npm dist-tag for the alpha.

Guard against publishing this prerelease as npm “latest”.

   "publishConfig": {
-    "access": "public"
+    "access": "public",
+    "tag": "next"
   },
packages/hd-transport-lowlevel/package.json (1)

8-10: Add an npm dist-tag for the alpha.

Prevent accidental “latest” tagging on prereleases.

   "publishConfig": {
-    "access": "public"
+    "access": "public",
+    "tag": "next"
   },
packages/hd-ble-sdk/package.json (1)

13-15: Add an npm dist-tag for the alpha.

Keep prereleases off the “latest” tag.

   "publishConfig": {
-    "access": "public"
+    "access": "public",
+    "tag": "next"
   },
packages/core/package.json (1)

14-16: Add an npm dist-tag for the alpha.

Same rationale: avoid publishing this as “latest”.

   "publishConfig": {
-    "access": "public"
+    "access": "public",
+    "tag": "next"
   },
packages/connect-examples/electron-example/package.json (2)

10-21: Rebuild native modules automatically for Electron.

Ensure native deps (e.g., HCI socket) match Electron’s ABI in dev and CI.

   "scripts": {
     "copy:inject": "node scripts/copy-injected.js",
     "clean:build": "rimraf out",
+    "postinstall": "electron-builder install-app-deps",
     "dev-electron-web": "cross-env NODE_ENV=development yarn copy:inject && yarn build:main && cd ../expo-example && yarn dev:electron-web",

22-30: Pin vs caret for noble in the example.

If you prefer getting patches automatically in the example app, switch to a caret. Keep it pinned if reproducibility is more important.

-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
♻️ Duplicate comments (2)
packages/hd-transport-electron/package.json (1)

23-26: Declare HCI socket as an optional peer for Linux users.

Most Linux setups need @stoprocent/bluetooth-hci-socket at runtime. Add it as an optional peer to guide integrators without forcing installs on macOS/Windows.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },
packages/connect-examples/electron-example/package.json (1)

23-30: Add HCI socket to dependencies to avoid runtime module-not-found on Linux.

@noble requires the HCI backend on Linux. The example app should depend on it explicitly.

   "dependencies": {
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",
     "electron-is-dev": "^3.0.1",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6927b30 and bced2ce.

📒 Files selected for processing (15)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: publish-and-release
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (14)
packages/shared/package.json (1)

3-3: PublishConfig tag for prerelease

  • Version bump to 1.1.10-alpha.2 looks good.
  • Add "tag": "next" so npm won’t mark this prerelease as latest.

Apply this diff in packages/shared/package.json:

 "publishConfig": {
-  "access": "public"
+  "access": "public",
+  "tag": "next"
 },

Verified no lingering "1.1.9" pins in any package.json. Ready to merge.

packages/hd-transport/package.json (1)

3-3: Version bump acknowledged.

No issues spotted with the version alignment here.

packages/hd-ble-sdk/package.json (1)

3-3: All BLE SDK dependencies are correctly pinned to 1.1.10-alpha.2

I’ve checked every package.json across examples, wrappers, and SDKs. Every reference to @onekeyfe/hd-core, hd-shared, and hd-transport-react-native is locked at 1.1.10-alpha.2. No mismatches detected—no further action needed.

packages/core/package.json (1)

3-3: Core alpha bump OK; double-check prerelease consumption plan.

The deps point to the same alpha. Confirm downstreams expect an alpha and won’t auto-range to a stable ^1.1.x while missing prerelease features.

Also applies to: 28-29

packages/hd-transport-react-native/package.json (1)

3-3: Version bump and deps alignment look good.

The package version and internal deps are consistently pinned to 1.1.10-alpha.2. No concerns from this manifest.

Also applies to: 22-24

packages/hd-transport-emulator/package.json (1)

3-3: Alpha alignment LGTM.

Version and internal deps moved to 1.1.10-alpha.2. Manifest changes are clean.

Also applies to: 27-28

packages/connect-examples/expo-playground/package.json (1)

3-3: Example app versions are in sync.

Bumped to 1.1.10-alpha.2 across SDK deps. Looks consistent for the demo.

Also applies to: 20-22

packages/hd-transport-web-device/package.json (1)

3-3: Web-device transport pinned correctly.

Runtime deps and the electron devDependency are aligned to 1.1.10-alpha.2. No issues spotted.

Also applies to: 23-24, 27-27

packages/hd-web-sdk/package.json (1)

3-3: All clear—no action needed

I reran the checks:

  • No mismatched @onekeyfe/hd-* versions.
  • No leftover @abandonware/noble.
  • All @stoprocent/noble imports live in electron code (hd-transport-electron and its example).
  • No runtime dependency on @onekeyfe/hd-transport-electron outside its own package.

Everything’s tidy. Merging is safe.

packages/hd-transport-electron/package.json (1)

3-3: Version bump looks good.

Matches the repo-wide alpha bump and keeps transports aligned.

packages/hd-transport-http/package.json (1)

3-3: Alpha version bump is consistent.

No concerns.

packages/connect-examples/electron-example/package.json (1)

10-21: Webpack externals mapping is correct

I’ve confirmed that webpack.config.ts declares both
'@stoprocent/noble': 'commonjs @stoprocent/noble' and
'@stoprocent/bluetooth-hci-socket': 'commonjs @stoprocent/bluetooth-hci-socket'
in the externals block (lines 51–52). No further changes needed.

packages/connect-examples/expo-example/package.json (1)

22-25: Version alignment across SDKs looks consistent.

The Expo example tracks the alpha.2 line correctly.

packages/hd-common-connect-sdk/package.json (1)

23-28: Good: all transport/core deps aligned to alpha.2.

Keeps the SDK coherent across packages.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (17)
packages/hd-transport-electron/src/noble-ble-handler.ts (17)

21-21: Type imports may fail if @stoprocent/noble lacks TypeScript definitions

The types Peripheral, Service, and Characteristic are imported directly from @stoprocent/noble. If this package doesn't ship with TypeScript definitions, the build will break.


27-27: Fix typo in comment

"bundlinpissues" should be "bundling issues".


115-120: Reduce notification log noise to debug level

Logging every notification at info level creates excessive noise and hurts performance.

-  logger?.info('[NobleBLE] Notification', {
+  logger?.debug('[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

705-708: Normalize connect error in force reconnect

The error callback needs safe handling.

-   peripheral.connect((error: Error | undefined) => {
+   peripheral.connect((error: unknown) => {
      if (error) {
        logger?.error('[NobleBLE] Force reconnect failed:', error);
-       reject(new Error(`Force reconnect failed: ${error.message}`));
+       reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));

755-758: Fresh peripheral connection error needs normalization

-   freshPeripheral.connect((error: Error | undefined) => {
+   freshPeripheral.connect((error: unknown) => {
      if (error) {
-       reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+       reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));

970-976: Main connection error needs normalization

-   connectedPeripheral.connect(async (error: Error | undefined) => {
+   connectedPeripheral.connect(async (error: unknown) => {
      clearTimeout(timeout);

      if (error) {
        logger?.error('[NobleBLE] Connection failed:', error);
-       reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+       reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));

1112-1116: Normalize chunk write error

-   writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+   writeCharacteristic.write(chunk, true, (error: unknown) => {
      if (error) {
        logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-       reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+       reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));

1405-1405: Log byte count, not hex string length

hexData.length counts characters, not bytes. Divide by 2 for actual byte count.

-       logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+       logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

68-81: Make isPairingError accept unknown to prevent crashes

The function assumes error is an Error object with a .message property. If callbacks pass strings or other types, this will crash.

-function isPairingError(error: Error): boolean {
+function isPairingError(error: unknown): boolean {
   const keywords = [
     'authentication',
     'pairing',
     'bonding',
     'insufficient',
     'security',
     'authorization',
     'permission',
     'access denied',
   ];
-  const errorMessage = error.message.toLowerCase();
+  const errorMessage = toErrorMessage(error).toLowerCase();
   return keywords.some(k => errorMessage.includes(k));
 }

Add this helper near the imports:

function toErrorMessage(err: unknown): string {
  if (typeof err === 'string') return err;
  if (err && typeof err === 'object' && 'message' in err) {
    return (err as any).message as string;
  }
  return String(err);
}

601-608: Handle non-Error callback values safely

The service discovery callback assumes error is an Error. Noble callbacks can pass strings or other types.

  peripheral.discoverServices(
    ONEKEY_SERVICE_UUIDS,
-   (error: Error | undefined, services: Service[]) => {
+   (error: unknown, services: Service[]) => {
      if (error) {
        logger?.error('[NobleBLE] Service discovery failed:', error);
-       reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+       reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
        return;
      }

623-627: Normalize characteristic discovery error handling

Same issue - the callback error needs normalization.

-   (error: Error | undefined, characteristics: Characteristic[]) => {
+   (error: unknown, characteristics: Characteristic[]) => {
      if (error) {
        logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-       reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+       reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
        return;
      }

1055-1055: Consider using with-response writes for single packets

Single writes currently use withoutResponse=true. For small control packets, with-response writes detect link issues earlier.


1058-1070: isPairingError expects Error type but callback may pass unknown

The write callback error could be a string or other type. Normalize before calling isPairingError.

-   writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+   writeCharacteristic.write(buffer, true, (error: unknown) => {
      if (error) {
        logger?.error('[NobleBLE] Write single failed:', error);
-       if (isPairingError(error)) {
+       const errMsg = toErrorMessage(error);
+       if (isPairingError(error)) {
          reject(
            ERRORS.TypedError(
              HardwareErrorCode.BleWriteCharacteristicError,
-             `Pairing required for write operation: ${error.message}`
+             `Pairing required for write operation: ${errMsg}`
            )
          );
        } else {
-         reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+         reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, errMsg));
        }

1104-1111: Don't log full chunk payloads - sensitive data risk

Logging full hex data for every chunk leaks sensitive information and bloats logs.

+     const preview = chunk.toString('hex').slice(0, 32);
      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
        deviceId,
        chunkIndex,
        chunkSize: chunk.length,
-       chunkData: chunk.toString('hex'),
+       chunkPreview: `${preview}...`,
        withResponse: false,
      });

1164-1170: Guard against concurrent unsubscribe race condition

If unsubscribe is in progress and subscribe is called, they'll race. Wait for idle state first.

  const opState = subscriptionOperations.get(deviceId);
  if (opState === 'subscribing') {
    // Subscription in progress; update callback and return
    notificationCallbacks.set(deviceId, callback);
    return Promise.resolve();
  }
+ if (opState === 'unsubscribing') {
+   // Wait until unsubscribe completes
+   while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+     await wait(50);
+   }
+ }

1217-1230: Handle unknown error types in subscribe/unsubscribe callbacks

The callbacks assume Error objects but may receive strings or other types.

  async function rebuildAppSubscription(
    deviceId: string,
    notifyCharacteristic: Characteristic
  ): Promise<void> {
    notifyCharacteristic.removeAllListeners('data');
-   await new Promise<void>(resolve => {
-     notifyCharacteristic.unsubscribe(() => {
-       resolve();
-     });
-   });
+   await new Promise<void>((resolve, reject) => {
+     notifyCharacteristic.unsubscribe((error?: unknown) => {
+       if (error) {
+         logger?.error('[NobleBLE] Unsubscribe before resubscribe failed:', error);
+         return reject(new Error(toErrorMessage(error)));
+       }
+       resolve();
+     });
+   });
    await new Promise<void>((resolve, reject) => {
-     notifyCharacteristic.subscribe((error?: Error) => {
+     notifyCharacteristic.subscribe((error?: unknown) => {
        if (error) {
-         reject(error);
+         reject(new Error(toErrorMessage(error)));
          return;
        }
        resolve();
      });
    });

1323-1323: Normalize unsubscribe callback error

-   notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+   notifyCharacteristic.unsubscribe((error: unknown) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ed32172 and 896b85d.

📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (20 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: Analyze (javascript-typescript)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (7)
packages/hd-transport-electron/package.json (2)

23-26: Declare Linux HCI backend as an optional peer for clearer integrator guidance.

Most Linux users will need @stoprocent/bluetooth-hci-socket at runtime. Expose it as an optional peer so consumers get a helpful install hint without breaking macOS/Windows.

Add this to package.json (no need to import it directly in code):

{
  "peerDependencies": {
    "electron": ">=25.0.0",
    "electron-log": ">=4.0.0",
    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
  },
  "peerDependenciesMeta": {
    "@stoprocent/bluetooth-hci-socket": {
      "optional": true
    }
  }
}

28-29: Prefer a caret range for @stoprocent/noble to receive patch fixes automatically.

Pinning to 2.3.4 blocks patch updates. Consider using a caret.

-    "@stoprocent/noble": "2.3.4"
+    "@stoprocent/noble": "^2.3.4"
packages/connect-examples/electron-example/package.json (2)

23-23: Add the Linux HCI backend explicitly to avoid “module not found” at runtime.

Example apps often don’t get optional deps pulled in. Add @stoprocent/bluetooth-hci-socket to dependencies so Linux users can run out of the box.

   "dependencies": {
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",

23-23: Add the HCI socket dependency to the Electron example for Linux

The webpack externals are correctly pulling in all @stoprocent/* modules (including @stoprocent/noble). However, the example isn’t installing or bundling the HCI socket library, which will cause BLE to fail on Linux.

• In packages/connect-examples/electron-example/package.json, under dependencies, add:

   "dependencies": {
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^<latest-version>",
     "debug": "4.3.4",
     …
   },

• Confirm that this new dependency is included in your production build. If you’re manually overriding files in electron-builder.config.js, make sure node_modules/@stoprocent/bluetooth-hci-socket/** is matched (or remove restrictive globs so all node_modules come along).

With that change, the HCI socket will be installed and bundled for Linux, and your BLE stack will work out of the box.

packages/hd-transport/package.json (1)

4-4: Replace placeholder description for package clarity.

Same feedback as earlier review: let’s swap the TODO for a concise, user-facing summary.

-  "description": "> TODO: description",
+  "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
packages/hd-transport-http/package.json (1)

27-31: Upgrade axios (security fixes) and re-audit.

axios ^0.27.2 is outdated and carries known advisories. Bump to 1.x and re-run audits.

Apply:

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.3",
     "@onekeyfe/hd-transport": "1.1.10-alpha.3",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }

Verify with:

#!/bin/bash
set -e
cd packages/hd-transport-http
npm install --package-lock-only --legacy-peer-deps
npm ls axios || true
npm audit --omit=dev || true
packages/hd-transport-lowlevel/package.json (1)

22-24: Consider workspace protocol for internal deps.

Switching to "workspace:*" reduces churn on every coordinated release. Only do this if your package manager setup supports it.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.3",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.3"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 896b85d and 4cfb684.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (15)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (13)
packages/shared/package.json (1)

3-3: Version bump looks good.

No API surface changes here. This aligns with the repo-wide move to 1.1.10-alpha.3.

packages/connect-examples/expo-playground/package.json (1)

3-3: Example app versions aligned with SDK bump.

The example now targets 1.1.10-alpha.3 across core/shared/web-sdk. Good for testing the BLE migration end-to-end.

Also applies to: 20-22

packages/hd-common-connect-sdk/package.json (1)

3-3: SDK aggregator versions synced.

All internal deps moved to 1.1.10-alpha.3. No issues spotted.

Also applies to: 23-28

packages/hd-transport-react-native/package.json (1)

3-3: Lockstep bumps confirmed; no alpha.2 remains.

All "version" fields and internal deps now use 1.1.10-alpha.3. No stragglers found. Great job—approving.

packages/hd-transport-emulator/package.json (1)

3-3: Consistent versioning across emulator and deps.

Bump matches the rest of the monorepo. No action needed.

Also applies to: 27-28

packages/hd-transport/package.json (1)

3-3: Version bump acknowledged.

No functional changes here. All good.

packages/core/package.json (1)

3-3: Core and deps in sync.

Version and internal dependency bumps are consistent with the release train.

Also applies to: 28-29

packages/hd-transport-http/package.json (1)

3-3: Monorepo version alignment looks good.

The package version matches the alpha.3 stream across the workspace.

packages/hd-transport-lowlevel/package.json (1)

3-3: Version bump is consistent.

Matches the repo-wide 1.1.10-alpha.3 line.

packages/connect-examples/expo-example/package.json (1)

3-3: OK to bump example app version.

Keeps the example aligned with the SDK release train.

packages/hd-ble-sdk/package.json (1)

3-3: BLE SDK version bump looks good.

No concerns with the alpha.3 alignment.

packages/hd-web-sdk/package.json (2)

3-3: Version alignment acknowledged.

The sdk tracks alpha.3 with the rest of the workspace.


24-27: Lockstep internal deps make sense here.

For a public web SDK, exact internal pins help avoid mismatches. No change needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/hd-transport-electron/src/noble-ble-handler.ts (1)

420-471: Targeted scan leaks the 'discover' listener on timeout; incorrect pre-removal

You add onDiscover, but on timeout you don’t remove it. Also calling removeListener(onDiscover) before adding it does nothing. Remove any prior listeners first, and always detach on timeout.

   return new Promise((resolve, reject) => {
     const timeout = setTimeout(() => {
       if (noble) {
         noble.stopScanning();
       }
       logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
+      // Detach the temporary listener to avoid leaks
+      if (noble) {
+        noble.removeListener('discover', onDiscover);
+      }
       resolve(null);
     }, FAST_SCAN_TIMEOUT);
@@
-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    // Remove existing discover listeners to prevent duplication
+    if (noble) {
+      noble.removeAllListeners('discover');
+      noble.on('discover', onDiscover);
+    }
@@
-    if (noble) {
-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+    if (noble) {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: unknown) => {
         if (error) {
           clearTimeout(timeout);
           if (noble) {
             noble.removeListener('discover', onDiscover);
           }
           logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, safeToLowerMessage(error)));
           return;
         }

Also applies to: 452-456

packages/hd-transport-electron/package.json (1)

16-21: Ensure native addons are rebuilt for Electron

@noble often pulls native pieces. Add an Electron rebuild step so binaries match your Electron ABI.

   "scripts": {
-    "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
-    "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
+    "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
+    "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket || true",
     "lint": "eslint .",
     "lint:fix": "eslint . --fix"
   },

If you rely on prebuilt binaries instead, document the process and skip this script.

packages/connect-examples/electron-example/package.json (1)

32-40: Confirm native rebuild for Electron.

If @stoprocent/bluetooth-hci-socket compiles natively, add a postinstall step or docs to run electron-rebuild for your Electron version to prevent ABI mismatches.

You can add:

   "scripts": {
+    "postinstall": "electron-rebuild -f -w @stoprocent/bluetooth-hci-socket || true",

And ensure electron-rebuild is a devDependency.

♻️ Duplicate comments (30)
packages/hd-transport/package.json (1)

3-5: Nice: description fixed and version aligned.

This addresses the earlier “TODO description” feedback and keeps prerelease versions consistent. Nothing else to change here.

packages/hd-transport-lowlevel/package.json (1)

3-3: Use workspace: for internal deps (same prior feedback).*

Repeating earlier guidance here to reduce friction on the next version bump.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.4"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }

Also applies to: 22-23

packages/hd-transport-electron/src/utils/blePairing.ts (6)

9-13: Make write response mode configurable (don’t hardcode withoutResponse=true)

Expose a withoutResponse option and log the chosen mode. On some stacks (especially Windows), a with-response write improves pairing reliability.

 export interface Step3Options {
   intervalMs?: number; // default 3000
   maxCycles?: number; // default 10
   initDataHex?: string; // hex string to write; default OneKey init
+  withoutResponse?: boolean; // default true
 }
@@
-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const withoutResponse = options.withoutResponse ?? true;
@@
-          writeCharacteristic.write(buffer, true, (writeError?: Error) => {
-            logger?.info(
-              `[Pairing] Listeners after attach: ${notifyCharacteristic.listenerCount('data')}`
-            );
+          writeCharacteristic.write(buffer, withoutResponse, (writeError?: unknown) => {
+            logger?.info(
+              `[Pairing] Listeners after attach: ${notifyCharacteristic.listenerCount('data')}, withoutResponse: ${withoutResponse}`
+            );

Also applies to: 25-27, 90-95


33-44: Use a portable timer type and unsubscribe on cleanup

NodeJS.Timeout clashes with DOM typings in Electron. Also unsubscribe the characteristic to leave a clean state and prevent lingering subscriptions.

-    let intervalId: NodeJS.Timeout | null = null;
+    let intervalId: ReturnType<typeof setInterval> | null = null;
@@
-    const cleanup = () => {
+    const cleanup = () => {
       if (intervalId) clearInterval(intervalId);
-      // pairing阶段结束前,先移除一次性侦听器,避免泄露
+      // During pairing, remove one-shot listeners to avoid leaks
       logger?.info(
-        `[Pairing] Cleaning up ${notifyCharacteristic.listenerCount('data')} listeners before cycle`
+        `[Pairing] Cleaning up ${notifyCharacteristic.listenerCount('data')} listeners`
       );
       notifyCharacteristic.removeAllListeners('data');
+      // Best-effort unsubscribe to avoid transient duplicate subscriptions
+      try {
+        notifyCharacteristic.unsubscribe(() => {});
+      } catch {
+        /* noop */
+      }
     };

58-67: Prevent overlapping probe cycles (race/leak risk)

setInterval can start a new unsubscribe→subscribe→write while the previous cycle is still running. Gate re-entry with an in-flight flag and ensure it resets on all paths. Also mark resolved before rejecting on timeout.

-    const doCycle = () => {
+    let inFlight = false;
+    const doCycle = () => {
-      if (resolved || responseReceived) return;
+      if (resolved || responseReceived || inFlight) return;
+      inFlight = true;
       cycles += 1;
@@
-      // 取消订阅 → 重新订阅 → 写入
+      // Unsubscribe → subscribe → write
       notifyCharacteristic.unsubscribe(() => {
-        notifyCharacteristic.subscribe((subscribeError?: Error) => {
+        notifyCharacteristic.subscribe((subscribeError?: unknown) => {
           if (subscribeError) {
-            logger?.error('[Pairing] subscribe failed', {
+            logger?.error('[Pairing] subscribe failed', {
               deviceId,
               cycle: cycles,
-              error: subscribeError.message,
+              error: toErrorMessage(subscribeError),
             });
-            return; // 等待下一轮
+            inFlight = false;
+            return; // wait for next round
           }
@@
-          writeCharacteristic.write(buffer, true, (writeError?: Error) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError?: unknown) => {
             logger?.info(
               `[Pairing] Listeners after attach: ${notifyCharacteristic.listenerCount('data')}`
             );
 
             if (writeError) {
               logger?.error('[Pairing] write failed', {
                 deviceId,
                 cycle: cycles,
-                error: writeError.message,
+                error: toErrorMessage(writeError),
               });
-              return; // 等待下一轮
+              inFlight = false;
+              return; // wait for next round
             }
             logger?.info('[Pairing] write', { deviceId, cycle: cycles });
+            inFlight = false;
           });
         });
       });
@@
-      // 达到最大轮次后,停止继续调度,立即判定为超时
+      // Stop scheduling when reaching max cycles and resolve/reject once
       if (cycles >= maxCycles && intervalId) {
         clearInterval(intervalId);
         intervalId = null;
         if (!resolved && !responseReceived) {
           const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
           logger?.info('[Pairing] timeout', { deviceId, cycles, elapsed: totalElapsed });
           cleanup();
-          reject(new Error('pairing_timeout'));
+          resolved = true;
+          reject(new Error('pairing_timeout'));
         }
       }
     };
@@
-    // 立即开始第一轮,然后每 intervalMs 一轮
+    // Start immediately, then repeat every intervalMs
     doCycle();
     intervalId = setInterval(doCycle, intervalMs);

Also applies to: 75-86, 90-101, 108-118, 121-124


75-85: Normalize callback errors (avoid .message on non-Errors)

Noble callbacks can pass strings. Use unknown and toErrorMessage().

-        notifyCharacteristic.subscribe((subscribeError?: Error) => {
+        notifyCharacteristic.subscribe((subscribeError?: unknown) => {
           if (subscribeError) {
-            logger?.error('[Pairing] subscribe failed', { deviceId, cycle: cycles, error: subscribeError.message });
+            logger?.error('[Pairing] subscribe failed', { deviceId, cycle: cycles, error: toErrorMessage(subscribeError) });
             return;
           }
@@
-          writeCharacteristic.write(buffer, true, (writeError?: Error) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError?: unknown) => {
             if (writeError) {
-              logger?.error('[Pairing] write failed', { deviceId, cycle: cycles, error: writeError.message });
+              logger?.error('[Pairing] write failed', { deviceId, cycle: cycles, error: toErrorMessage(writeError) });
               return;
             }

Also applies to: 90-101


39-44: Unify comment language and phrasing

Keep comments in clear English for consistency and readability.

-      // pairing阶段结束前,先移除一次性侦听器,避免泄露
+      // During pairing, remove one-shot listeners to avoid leaks
@@
-      // 清理旧监听,确保不会累积
+      // Clean previous listener to avoid accumulation
@@
-      // 取消订阅 → 重新订阅 → 写入
+      // Unsubscribe → resubscribe → write
@@
-          // 仅监听一次本轮的回包
+          // Listen only once for this cycle's response
@@
-    // 立即开始第一轮,然后每 intervalMs 一轮
+    // Start immediately, then run every intervalMs

Also applies to: 68-70, 75-76, 87-89, 121-124


6-7: Import types and error normalizer from a stable source

Importing Characteristic types from @stoprocent/noble may fail if the fork doesn’t publish TS types. Also add a normalizer to avoid unsafe .message access. If you already ship a local shim, ignore the first line change; still add the normalizer import.

-import type { Characteristic } from '@stoprocent/noble';
-import type { Logger } from '../types/noble-extended';
+// Prefer local shim or upstream 'noble' types to avoid missing d.ts in forks
+import type { Characteristic } from 'noble';
+import type { Logger } from '../types/noble-extended';
+import { toErrorMessage } from '../types/noble-extended';
packages/hd-transport-electron/src/noble-ble-handler.ts (15)

596-676: Normalize discovery callback errors; type as unknown

Avoid .message on non-Errors for both service and characteristic discovery.

-    peripheral.discoverServices(
-      ONEKEY_SERVICE_UUIDS,
-      (error: Error | undefined, services: Service[]) => {
+    peripheral.discoverServices(
+      ONEKEY_SERVICE_UUIDS,
+      (error: unknown, services: Service[]) => {
         if (error) {
-          logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          logger?.error('[NobleBLE] Service discovery failed:', error);
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, safeToLowerMessage(error)));
           return;
         }
@@
-        service.discoverCharacteristics(
-          [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+        service.discoverCharacteristics(
+          [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
-              logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              logger?.error('[NobleBLE] Characteristic discovery failed:', error);
+              reject(
+                ERRORS.TypedError(
+                  HardwareErrorCode.BleCharacteristicNotFound,
+                  safeToLowerMessage(error)
+                )
+              );
               return;
             }

684-709: Normalize force reconnect error

Use a safe message; don’t assume Error.

-  await new Promise<void>((resolve, reject) => {
-    peripheral.connect((error: Error | undefined) => {
+  await new Promise<void>((resolve, reject) => {
+    peripheral.connect((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${safeToLowerMessage(error)}`));
       } else {

971-999: Connection callback: treat error as unknown and normalize

Avoid .message directly; normalize before logging and rejecting.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, safeToLowerMessage(error)));
         return;
       }

1325-1341: Unsubscribe: callback error should be unknown; normalize message

Keep logs stable and avoid property access on non-Errors.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

21-21: Import types from a package that guarantees .d.ts availability

@stoprocent/noble may not ship types. Import TS types from 'noble' (or your local shim) while keeping runtime require to '@stoprocent/noble'.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

27-27: Fix typo in comment

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

67-81: Broaden isPairingError to accept unknown; avoid unsafe .message

Prevent crashes when non-Error values flow through callbacks. Normalize once and test keywords.

-function isPairingError(error: Error): boolean {
+function isPairingError(error: unknown): boolean {
   const keywords = [
     'authentication',
     'pairing',
     'bonding',
     'insufficient',
     'security',
     'authorization',
     'permission',
     'access denied',
   ];
-  const errorMessage = error.message.toLowerCase();
+  const errorMessage = safeToLowerMessage(error);
   return keywords.some(k => errorMessage.includes(k));
}
+
+function safeToLowerMessage(err: unknown): string {
+  const msg =
+    typeof err === 'string'
+      ? err
+      : (err as any)?.message && typeof (err as any).message === 'string'
+      ? (err as any).message
+      : String(err);
+  return msg.toLowerCase();
+}

115-120: Reduce notification log noise; prefer debug level

Per-notification info logs are noisy and can hurt perf.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

754-758: Fresh peripheral connect: normalize error message

Same pattern as above.

-      freshPeripheral.connect((error: Error | undefined) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${safeToLowerMessage(error)}`));
         } else {

1055-1070: Single write: prefer with-response or make it configurable; normalize errors

For control packets, with-response (false) often detects link issues earlier. At minimum, normalize errors.

-    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length });
+    const withoutResponse = false; // safer for small control packets; consider making this configurable
+    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length, withoutResponse });
@@
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
-          logger?.error('[NobleBLE] Write single failed:', error);
-          if (isPairingError(error)) {
+          logger?.error('[NobleBLE] Write single failed:', error);
+          if (isPairingError(error)) {
             reject(
               ERRORS.TypedError(
                 HardwareErrorCode.BleWriteCharacteristicError,
-                `Pairing required for write operation: ${error.message}`
+                `Pairing required for write operation: ${safeToLowerMessage(error)}`
               )
             );
           } else {
-            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+            reject(
+              ERRORS.TypedError(
+                HardwareErrorCode.BleWriteCharacteristicError,
+                safeToLowerMessage(error)
+              )
+            );
           }
           return;
         }

1094-1116: Chunked write: don’t log full payloads; normalize write errors

Logging entire hex leaks data and is noisy. Log previews and sizes only. Also handle error as unknown.

-  logger?.info('[NobleBLE] Splitting into chunks:', {
+  logger?.info('[NobleBLE] Splitting into chunks:', {
     deviceId,
     totalChunks: chunks.length,
     chunkSize: BLE_PACKET_SIZE,
     totalDataLength: buffer.length,
   });
@@
-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
+        chunkPreview: `${preview}...`,
         withResponse: false,
       });
@@
-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
           logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          reject(
+            ERRORS.TypedError(
+              HardwareErrorCode.BleWriteCharacteristicError,
+              safeToLowerMessage(error)
+            )
+          );
           return;
         }
         resolve();
       });

1164-1169: Guard subscribe against concurrent unsubscribe

If unsubscribe is in-flight, wait until idle to avoid races.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
@@
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    // Wait until unsubscribe completes
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1212-1231: Rebuild subscription: normalize subscribe/unsubscribe callback errors

Treat callback errors as unknown; wrap with a safe message and log.

-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) {
+          logger?.error('[NobleBLE] Unsubscribe before resubscribe failed:', error);
+          return reject(new Error(safeToLowerMessage(error)));
+        }
+        resolve();
+      });
+    });
     await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
         if (error) {
-          reject(error);
+          reject(new Error(safeToLowerMessage(error)));
           return;
         }
         resolve();
       });
     });

1277-1288: Probe write (non-Windows): normalize error callback

Don’t assume Error in the callback; log a safe message.

-      await new Promise<void>((resolve, reject) => {
-        characteristics.write.write(probeData, false, error => {
+      await new Promise<void>((resolve) => {
+        characteristics.write.write(probeData, false, (error: unknown) => {
           if (error) {
             // We can often ignore errors here, as the goal is just to trigger the dialog.
             // However, log it for debugging purposes.
-            logger?.warn('[NobleBLE] Probe write failed (this may be expected):', error.message);
+            logger?.warn('[NobleBLE] Probe write failed (this may be expected):', safeToLowerMessage(error));
           }
           resolve(); // Resolve regardless of error
         });
       });

1407-1408: IPC write log should report bytes, not hex characters

hexData.length counts nibbles. Divide by 2 for bytes.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });
packages/hd-transport-web-device/package.json (1)

23-24: Use workspace protocol for internal deps to cut churn

Since this is a monorepo, prefer workspace:^ for internal packages to keep versions in lockstep and avoid manual bumps.

-    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.4"
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^"
@@
-    "@onekeyfe/hd-transport-electron": "1.1.10-alpha.4",
+    "@onekeyfe/hd-transport-electron": "workspace:^",

Also applies to: 27-27

packages/hd-transport-electron/package.json (2)

23-26: Add optional peer for Linux HCI socket

Guide Linux users without forcing installs on macOS/Windows.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

28-30: Let noble pick up patch fixes automatically

Use a caret to receive non-breaking updates.

-    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
-    "@stoprocent/noble": "2.3.4"
+    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
+    "@stoprocent/noble": "^2.3.4"
packages/connect-examples/electron-example/package.json (1)

23-30: Add @stoprocent/bluetooth-hci-socket to avoid runtime failures on Linux.

Noble needs the HCI socket in Electron main on Linux. It’s not present here, so you risk "module not found".

Suggested fix:

   "dependencies": {
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "2.2.3",
     "debug": "4.3.4",

Alternatively, if you prefer to keep it optional per platform:

   "dependencies": {
     "@stoprocent/noble": "2.3.4",
     "debug": "4.3.4",
     ...
   },
+  "optionalDependencies": {
+    "@stoprocent/bluetooth-hci-socket": "2.2.3"
+  },

Also ensure your packager doesn’t prune optional deps you actually need at runtime.

packages/hd-transport-http/package.json (1)

27-31: Upgrade axios to a maintained 1.x release.

0.27.x is old and has known advisories. Move to axios 1.x and run tests.

Proposed change:

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.4",
     "@onekeyfe/hd-transport": "1.1.10-alpha.4",
-    "axios": "^0.27.2",
+    "axios": "^1.6.0",
     "secure-json-parse": "^4.0.0"
   }

Pick the exact 1.x you support, update types if needed, and validate any response interceptor differences.

packages/connect-examples/expo-example/package.json (1)

22-25: Verify these exact versions exist before merging.

Examples should resolve without local workspaces. Double-check that all four packages are published at these versions.

Run:

#!/bin/bash
set -e
for pkg in @onekeyfe/[email protected] @onekeyfe/[email protected] @onekeyfe/[email protected] @onekeyfe/[email protected]; do
  echo "Checking $pkg"; npm view "$pkg" version
done
packages/hd-ble-sdk/package.json (1)

23-25: Unify internal dependency strategy: prefer workspace protocol or align all to the same prerelease.

You’re mixing stable (1.1.10) and alpha (.4) across tightly-coupled internal packages. This often causes subtle mismatches during local dev and publish ordering. In this monorepo, using the workspace protocol avoids that churn.

Option A (preferred): use workspaces to keep everything in lockstep.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.4"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }

Option B: if you must pin, at least align to the same prerelease tag across the set.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.4"
+    "@onekeyfe/hd-core": "1.1.10-alpha.4",
+    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
+    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.4"
   }

Quick check script to surface mixed stable/alpha versions across the repo:

#!/bin/bash
# List internal @onekeyfe/* dependency versions and highlight mixed tags.
fd package.json packages -t f -E "**/node_modules/**" \
| xargs -I{} jq -r '
  .name as $pkg
  | (.dependencies // {}) as $deps
  | to_entries
  | map(select(.key|test("^@onekeyfe/")))
  | .[]
  | "\($pkg)\t\(.key)\t\(.value)"
' {} \
| sort \
| awk -F'\t' '{
  key=$2; ver=$3;
  sub(/-.*/,"",base,ver); # naive trim after first dash for grouping
  arr[key][ver]++
  print
} END {
  print "\n-- Summary of versions per internal dependency --"
}'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 26e0d41 and 6d0f725.

📒 Files selected for processing (17)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (21 hunks)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (8)
packages/hd-transport-electron/src/utils/blePairing.ts (1)

56-57: Validate initDataHex to avoid Buffer.from throws

Guard against invalid hex. Log clearly if parsing fails.

-    const buffer = Buffer.from(initDataHex, 'hex');
+    let buffer: Buffer;
+    try {
+      buffer = Buffer.from(initDataHex, 'hex');
+    } catch (e) {
+      cleanup();
+      reject(new Error(`Invalid initDataHex: ${toErrorMessage(e)}`));
+      return;
+    }

Likely an incorrect or invalid review comment.

packages/connect-examples/expo-playground/package.json (1)

3-4: Version alignment: mixing stable and alpha can duplicate deps

You pin hd-core to 1.1.10 but use alpha.4 for hd-shared and hd-web-sdk. This can pull two trees if peer ranges don’t align. If intentional, ignore. Otherwise, align to a single train or switch internal deps to workspace:^.

Would you like me to scan the repo for mixed OneKey versions and list any mismatches?

Also applies to: 20-23

packages/hd-common-connect-sdk/package.json (1)

23-28: Verify package registry and version availability

The npm view checks against the public registry returned 404 for every package. It looks like these are hosted in a private registry. Before publishing, please:

  • Ensure your .npmrc (or CI config) points to the correct registry.
  • Confirm that versions 1.1.10 (hd-core) and 1.1.10-alpha.4 (all others) are actually published there.
  • Double-check that mixing a stable core with alpha transports/shared APIs won’t introduce compatibility or peer-dependency issues.
packages/core/package.json (2)

3-5: Nice: real description added.

Clear and helpful.


28-36: Do not bump axios in core alone

Core already uses axios ^0.27.2, just like HD Transport HTTP and HD Transport Emulator. Raising only core to 1.x would create drift.

• packages/core/package.json: "axios": "^0.27.2"
• packages/hd-transport-http/package.json: "axios": "^0.27.2"
• packages/hd-transport-emulator/package.json: "axios": "^0.27.2"

If you intend to upgrade to axios 1.x, apply that change across all packages in one coordinated PR. Otherwise, leave the versions as they are.

Likely an incorrect or invalid review comment.

packages/connect-examples/expo-example/package.json (1)

3-3: Version bump: OK.

Keep the example in sync with the monorepo release train.

packages/hd-ble-sdk/package.json (1)

3-3: Version bump looks consistent with the repo-wide prerelease.

No issues spotted with the package version update to 1.1.10-alpha.4.

packages/hd-web-sdk/package.json (1)

3-3: Version bump acknowledged.

Matches the repo’s alpha.4 cadence.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/package-publish.yml (3)

21-36: Set up Node in get-version job to avoid runner drift

This job runs node -p without installing Node. Relying on the runner’s preinstalled Node can break later. Add setup-node here for determinism.

       - name: Checkout
         uses: actions/checkout@v4
         with:
           fetch-depth: 0

+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '22'
+
       - name: Get version information
         id: version-info
         run: |

48-59: Enable Corepack and cache Yarn to speed up CI and pin Yarn version

If the repo uses Yarn Berry, enable Corepack and cache Yarn modules. This stabilizes the toolchain and cuts install time.

       - name: Setup Node.js
         uses: actions/setup-node@v4
         with:
           node-version: '22'
           registry-url: 'https://registry.npmjs.org'
+          cache: 'yarn'
 
+      - name: Enable Corepack (Yarn)
+        run: corepack enable
+
       - name: Install dependencies
         run: |
           echo "📦 Installing dependencies..."
-          yarn
+          yarn
           yarn bootstrap

If you’re on Yarn Berry, prefer yarn install --immutable over plain yarn to enforce lockfile fidelity.


60-67: Preflight: fail if any package@version already exists on npm

Prevent partial or duplicate publishes. Add a preflight step that checks every workspace package and exits if the version is already on npm.

       - name: Build packages
         run: |
           echo "🔨 Building packages..."
           yarn build
 
+
+      - name: Preflight check (prevent re-publish of existing versions)
+        env:
+          NPM_CONFIG_USERCONFIG: /dev/null
+        run: |
+          set -euo pipefail
+          echo "🔎 Checking if any package@version already exists on npm..."
+          node -e '
+            const { readdirSync, readFileSync, statSync } = require("fs");
+            const path = require("path");
+            const root = path.join(process.cwd(), "packages");
+            const pkgs = readdirSync(root).filter(d => statSync(path.join(root, d)).isDirectory());
+            const list = pkgs.map(d => {
+              const p = JSON.parse(readFileSync(path.join(root, d, "package.json"), "utf8"));
+              return { name: p.name, version: p.version };
+            });
+            console.log(list);
+          ' > packages.json
+          jq -r '.[] | "\(.name)@\(.version)"' packages.json | while read line; do
+            if npm view "$line" version >/dev/null 2>&1; then
+              echo "❌ Already published: $line"; exit 1;
+            fi
+          done
+          echo "✅ No already-published versions found."
♻️ Duplicate comments (5)
packages/hd-common-connect-sdk/package.json (1)

3-3: Add CHANGELOG entry and git tag for 1.1.10-alpha.4.

Mirror the version bump with release notes and a tag so downstreams can trace changes.

#!/bin/bash
# Look for a CHANGELOG entry and a matching tag
rg -n "## \\[1\\.1\\.10-alpha\\.4\\]" -C2 --glob '!**/node_modules/**' || echo "No CHANGELOG entry found"
git tag -l | rg "1\\.1\\.10-alpha\\.4" || echo "No git tag named 1.1.10-alpha.4"
packages/connect-examples/expo-example/package.json (1)

22-25: Sync example pins with actually published artifacts.

Same concern as earlier review: ensure these four packages exist at 1.1.10-alpha.4 to keep the example installable.

#!/bin/bash
set -euo pipefail
ver="1.1.10-alpha.4"
for p in "@onekeyfe/hd-ble-sdk" "@onekeyfe/hd-common-connect-sdk" "@onekeyfe/hd-core" "@onekeyfe/hd-web-sdk"; do
  echo -n "$p@$ver => "
  npm view "$p@$ver" version --silent || echo "NOT PUBLISHED"
done
packages/hd-ble-sdk/package.json (1)

23-25: Optional: adopt workspace protocol for internal deps.

This eases coordinated releases across the monorepo. Only do this if your publisher resolves workspace ranges correctly.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.4",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.4"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }

Additionally, verify this package is published before merging the example updates that consume it.

#!/bin/bash
npm view "@onekeyfe/[email protected]" version --silent || echo "NOT PUBLISHED"
packages/hd-web-sdk/package.json (2)

24-27: Aligned internal deps resolve prior “stable vs alpha” skew.

All @onekeyfe/hd-* deps now pin to 1.1.10-alpha.4. This addresses the earlier mismatch concern.


24-27: Optional: switch internal deps to workspace: during active development.*

Keeps local packages in lockstep and simplifies version bumps across the monorepo. If you prefer this approach, here’s the minimal diff:

-    "@onekeyfe/hd-core": "1.1.10-alpha.4",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.4",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.4",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.4"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"

Only adopt if your release process expects pinned versions to be written by a publish step.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6d0f725 and 6f48e27.

📒 Files selected for processing (7)
  • .github/workflows/package-publish.yml (1 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (5)
packages/connect-examples/expo-playground/package.json (1)

3-3: Version bump is fine for a private example.

Package is marked private. No publish/release artifacts needed here. Proceed.

packages/connect-examples/expo-example/package.json (1)

3-3: Version bump looks good.

Example remains private; no release artifacts required.

packages/core/package.json (1)

4-4: Nice—clear, user-facing description.

This improves package discoverability.

packages/hd-ble-sdk/package.json (1)

3-3: Version alignment OK.

Matches the monorepo’s alpha.4 wave.

packages/hd-web-sdk/package.json (1)

3-3: Verified: All packages updated to 1.1.10-alpha.4

  • No 1.1.10-alpha.2 pins remain.
  • All internal @onekeyfe/hd-* deps use 1.1.10-alpha.4 or workspace:*.
  • Connect examples in packages/connect-examples/expo-playground and expo-example reference 1.1.10-alpha.4.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.husky/pre-commit (1)

11-13: Fix NUL-delimited xargs usage

You pipe -z, but don’t pass -0 to xargs. Filenames with spaces will break.

Apply:

-git diff --name-only -z | xargs git add
+git diff --name-only -z | xargs -0 git add
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6f48e27 and 8767f38.

📒 Files selected for processing (3)
  • .husky/pre-commit (1 hunks)
  • package.json (2 hunks)
  • scripts/check-versions.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint (22)
🔇 Additional comments (1)
package.json (1)

33-35: Good addition; integrates the check into publish and dev flows

  • check-versions script is clear and discoverable.
  • publish-packages now guards releases. Nice.
  • dev:transport-electron improves DX.

No changes needed here.

Also applies to: 53-53

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/check-versions.js (1)

1-56: Ensure recursive package discovery in check-versions.js

The version checker currently only picks up 12 packages (maxdepth 2), but there are 15 package.json files under packages (it misses the three under packages/connect-examples) as confirmed by your script. We need to include those nested packages in the check.

● File to update: scripts/check-versions.js
● Problematic code (shallow scan):

const packageDirs = fs
  .readdirSync(packagesDir)
  .filter(file => fs.statSync(path.join(packagesDir, file)).isDirectory());

● Missing entries:
– packages/connect-examples/electron-example/package.json
– packages/connect-examples/expo-example/package.json
– packages/connect-examples/expo-playground/package.json

Action: Replace the shallow readdirSync + filter logic with a recursive directory traversal (or a glob) that finds all package.json under packagesDir (excluding node_modules, dist, build) before parsing and comparing versions.

♻️ Duplicate comments (3)
scripts/check-versions.js (3)

12-16: Shallow scan only inspects one level; misses nested packages

The script only reads packages/* (1-level). It skips nested workspaces like packages/connect-examples/*, giving false confidence.

Replace the one-level directory scan with a recursive finder. Add these helpers near the top (above main):

// Helpers
const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build']);

function findPackageJsonFiles(root) {
  const out = [];
  for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
    if (!entry.isDirectory()) continue;
    if (IGNORED_DIRS.has(entry.name)) continue;
    const dir = path.join(root, entry.name);
    const pkgJson = path.join(dir, 'package.json');
    if (fs.existsSync(pkgJson)) out.push(pkgJson);
    out.push(...findPackageJsonFiles(dir));
  }
  return out;
}

Then change the scan:

-  const packageDirs = fs.readdirSync(packagesDir).filter(file => {
-    const filePath = path.join(packagesDir, file);
-    return fs.statSync(filePath).isDirectory();
-  });
+  const pkgJsonFiles = findPackageJsonFiles(packagesDir);

17-38: Use a deterministic baseline and handle “no packages found”

Using the first encountered version as baseline is non-deterministic and can change across runs. Also, when nothing is found, firstVersion stays null.

Add a baseline helper and refactor the loop:

function getBaselineVersion(list) {
  if (!list.length) return null;
  const counts = new Map();
  for (const v of list) counts.set(v, (counts.get(v) || 0) + 1);
  let best = null, max = -1;
  for (const [v, c] of counts) if (c > max) { max = c; best = v; }
  return best;
}

Apply this diff to the collection loop:

-  for (const pkg of packageDirs) {
-    const pkgJsonPath = path.join(packagesDir, pkg, 'package.json');
+  for (const pkgJsonPath of pkgJsonFiles) {
     if (fs.existsSync(pkgJsonPath)) {
       try {
         const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
         const { name, version } = pkgJson;

         if (name && version) {
-          versions[name] = version;
-          if (!firstVersion) {
-            firstVersion = version;
-          }
-
-          if (version !== firstVersion) {
-            hasError = true;
-          }
+          versions[name] = version;
         }
       } catch (e) {
         console.error(`Error parsing ${pkgJsonPath}:`, e);
       }
     }
   }
+
+  const allVersions = Object.values(versions);
+  const baseline = getBaselineVersion(allVersions);
+  if (!baseline) {
+    console.error('❌ Error: No package.json files found under packages/.');
+    process.exit(1);
+  }
+  hasError = allVersions.some(v => v !== baseline);

40-52: Stabilize report output and messages

Sort package names for stable diffs. Report against the baseline, not the first seen. Update the success message accordingly.

-  if (hasError) {
+  if (hasError) {
     console.error('❌ Error: Found inconsistent package versions.');
     console.log('   Please ensure all packages have the same version.');
     console.log('\n--- Version Report ---');
-    for (const [name, version] of Object.entries(versions)) {
-      const marker = version === firstVersion ? '✅' : '❌';
-      console.log(`  ${marker} ${name}: ${version}`);
-    }
+    const namesSorted = Object.keys(versions).sort();
+    for (const name of namesSorted) {
+      const version = versions[name];
+      const marker = version === baseline ? '✅' : '❌';
+      console.log(`  ${marker} ${name}: ${version}`);
+    }
     console.log('----------------------\n');
     process.exit(1);
   } else {
-    console.log(`✅ All ${Object.keys(versions).length} packages are at version: ${firstVersion}`);
+    console.log(`✅ All ${Object.keys(versions).length} packages are at version: ${baseline}`);
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8767f38 and ceb8577.

📒 Files selected for processing (2)
  • packages/connect-examples/expo-example/webpack.config.js (1 hunks)
  • scripts/check-versions.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (1)
packages/connect-examples/expo-example/webpack.config.js (1)

15-15: Use ‘auto’ for publicPath in Electron production builds

Please update the production publicPath in packages/connect-examples/expo-example/webpack.config.js so that Electron builds loaded over file:// resolve assets correctly:

-    // Production: assets served from root or GitHub Pages
-    config.output.publicPath = isElectronMode ? '/' : '/expo-example/';
+    // In Electron (file://), use 'auto' to keep chunk/asset URLs relative
+    config.output.publicPath = isElectronMode ? 'auto' : '/expo-example/';

• File: packages/connect-examples/expo-example/webpack.config.js
• Line: 15

‘auto’ lets Webpack 5 compute a safe, runtime-relative path. After this change, please build the Expo example for Electron (no dev server) and confirm that all JS/CSS chunks load correctly under file://.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
packages/hd-transport-electron/src/noble-ble-handler.ts (6)

423-431: Targeted scan: listener leak on timeout and no-op removal

  • Timeout path never removes the onDiscover listener → leak.
  • Removing the same onDiscover before adding it is a no-op; it won’t clear prior closures.
   const timeout = setTimeout(() => {
     if (noble) {
       noble.stopScanning();
     }
     logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
+    // Ensure we remove the temporary listener to avoid leaks
+    try { noble?.removeListener('discover', onDiscover); } catch {}
     resolve(null);
   }, FAST_SCAN_TIMEOUT);
@@
-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    // Register a temporary listener for this targeted scan
+    noble?.on('discover', onDiscover);

Also applies to: 452-456


684-704: Force reconnect may emit a user-visible disconnect event mid-retry

You clean state first, then call peripheral.disconnect while the existing 'disconnect' listener is still attached. That will fire handleDeviceDisconnect and send a BLE_DEVICE_DISCONNECTED event to the renderer during an internal recovery. This can confuse the UI.

-// Step 1: Clean up all device state first
-cleanupDeviceState(deviceId);
-
-// Step 2: Force disconnect if connected
+// Step 1: Remove disconnect listener to avoid spurious UI events
+peripheral.removeAllListeners('disconnect');
+
+// Step 2: 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 3: Clear any remaining listeners on the peripheral
+// Step 3: Clean up all device state after disconnect
+cleanupDeviceState(deviceId);
+
+// Step 4: Clear any remaining listeners on the peripheral
 peripheral.removeAllListeners();
 
-// Step 4: Re-establish connection with longer timeout
+// Step 5: Re-establish connection with longer timeout

1348-1348: Remove stray console log

console.log('NOBLE_VERSION_771') looks like leftover debug.

-    console.log('NOBLE_VERSION_771');
+    // console.log removed (debug leftover)

129-156: Guard against pathological header lengths

Only negative lengths are rejected. Consider a sane upper bound to prevent unbounded buffer growth on malformed data.

-      // Only validate for negative lengths (which would be invalid)
-      if (packetState.bufferLength < 0) {
+      // Validate for invalid or excessive lengths
+      const MAX_PACKET_LENGTH = 1024 * 1024; // 1 MiB cap (tune as needed)
+      if (packetState.bufferLength < 0 || packetState.bufferLength > MAX_PACKET_LENGTH) {
         logger?.error('[NobleBLE] Invalid negative packet length detected:', {
           length: packetState.bufferLength,
           dataLength: data.length,
           rawHeader: data.subarray(0, Math.min(16, data.length)).toString('hex'),
           lengthBytes: data.subarray(5, 9).toString('hex'),
         });
         resetPacketState(packetState);
-        return { isComplete: false, error: 'Invalid packet length in header' };
+        return { isComplete: false, error: 'Invalid packet length in header' };
       }

1047-1052: Consider masking/shortening data previews

Even first 8 bytes or 32 nibbles can leak sensitive info. If payloads may contain secrets, gate previews behind a DEBUG flag and redact by default.

Also applies to: 1094-1100


831-836: Retry telemetry reads cleanly

onFailedAttempt logging is helpful. Consider including the actual cause code/message in addition to error.message for faster triage.

♻️ Duplicate comments (15)
packages/hd-transport-electron/src/noble-ble-handler.ts (15)

460-473: Start scanning callback: type error as unknown and normalize

Don’t assume Error. Preserve message safely.

-  noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+  noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: unknown) => {
     if (error) {
       clearTimeout(timeout);
-      logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-      reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+      logger?.error('[NobleBLE] Failed to start targeted scan:', error);
+      reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, toErrorMessage(error)));
       return;
     }

596-677: Discovery callbacks: handle error as unknown, normalize messages, tighten char matching

  • Use unknown for callback errors and normalize.
  • Current 4–8 substring keying is brittle. Prefer explicit matching against known full UUIDs (lowercased, dashless).
-    peripheral.discoverServices(
-      ONEKEY_SERVICE_UUIDS,
-      (error: Error | undefined, services: Service[]) => {
+    peripheral.discoverServices(
+      ONEKEY_SERVICE_UUIDS,
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
           return;
         }
@@
-        service.discoverCharacteristics(
-          [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+        service.discoverCharacteristics(
+          [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
               return;
             }
@@
-            // Find characteristics by extracting the distinguishing part of UUID
+            // Find characteristics by normalized UUID match (lowercased, dashless)
             for (const characteristic of characteristics) {
               const uuid = characteristic.uuid.replace(/-/g, '').toLowerCase();
-              const uuidKey = uuid.length >= 8 ? uuid.substring(4, 8) : uuid;
-
-              if (uuidKey === NORMALIZED_WRITE_UUID) {
+              if (uuid.endsWith(NORMALIZED_WRITE_UUID)) {
                 writeCharacteristic = characteristic;
-              } else if (uuidKey === NORMALIZED_NOTIFY_UUID) {
+              } else if (uuid.endsWith(NORMALIZED_NOTIFY_UUID)) {
                 notifyCharacteristic = characteristic;
               }
             }

970-999: Connection callback: treat error as unknown and normalize; ensure disconnect path is intentional

  • Use unknown for error and normalize messages.
  • Confirm that calling disconnect on discovery failure should notify the renderer (disconnect listener is armed).
-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }
@@
-      } catch (discoveryError) {
+      } catch (discoveryError) {
         logger?.error(
           '[NobleBLE] Service/characteristic discovery failed after all attempts:',
           discoveryError
         );
-        // Disconnect on failure
+        // Disconnect on failure (this will notify renderer via disconnect listener)
         connectedPeripheral.disconnect();
         reject(discoveryError);
       }

1047-1075: Single write: withResponse vs withoutResponse; normalize errors and pairing checks

  • You pass true (withoutResponse) which skips link-level ack. For control packets, with-response is safer unless you have perf constraints.
  • Normalize errors and avoid unsafe isPairingError call.
-  if (buffer.length <= BLE_PACKET_SIZE) {
-    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length });
+  if (buffer.length <= BLE_PACKET_SIZE) {
+    const withoutResponse = false; // safer for control/handshake writes
+    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length, withoutResponse });
     await wait(5);
     return new Promise((resolve, reject) => {
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
-          logger?.error('[NobleBLE] Write single failed:', error);
-          if (isPairingError(error)) {
+          logger?.error('[NobleBLE] Write single failed:', error);
+          const msg = toErrorMessage(error);
+          if (isPairingError(error)) {
             reject(
               ERRORS.TypedError(
                 HardwareErrorCode.BleWriteCharacteristicError,
-                `Pairing required for write operation: ${error.message}`
+                `Pairing required for write operation: ${msg}`
               )
             );
           } else {
-            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, msg));
           }
           return;
         }
 
         logger?.info('[NobleBLE] Write single ok', { deviceId });

1325-1342: Unsubscribe callback: handle error as unknown and normalize

Avoid accessing error.message directly.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

21-21: Import types from 'noble' (not '@stoprocent/noble') or add a local d.ts shim

Many forks of noble ship without .d.ts. Importing types from '@stoprocent/noble' can break type-checking in CI. Switch type-only imports to 'noble' while keeping runtime require on '@stoprocent/noble', or add a local module declaration that re-exports types.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

If you prefer to stick with '@stoprocent/noble', add a shim (packages/hd-transport-electron/src/types/@stoprocent__noble.d.ts):

declare module '@stoprocent/noble' {
  export * from 'noble';
}

27-27: Typo in comment

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

67-81: Broaden isPairingError input and avoid unsafe .message access

Callbacks can pass strings or unknown. Accessing error.message can throw. Normalize first.

-function isPairingError(error: Error): boolean {
+function isPairingError(error: unknown): boolean {
   const keywords = [
     'authentication',
     'pairing',
     'bonding',
     'insufficient',
     'security',
     'authorization',
     'permission',
     'access denied',
   ];
-  const errorMessage = error.message.toLowerCase();
+  const errorMessage = toErrorMessage(error).toLowerCase();
   return keywords.some(k => errorMessage.includes(k));
 }

Add helper (near imports, outside diff range):

function toErrorMessage(err: unknown): string {
  if (typeof err === 'string') return err;
  if (err && typeof err === 'object' && 'message' in (err as any) && typeof (err as any).message === 'string') {
    return (err as any).message as string;
  }
  try { return JSON.stringify(err); } catch { return String(err); }
}

115-120: Reduce high-frequency logs to debug level

Per-notification and per-packet-assembled info logs are noisy and can hurt perf.

-  logger?.info('[NobleBLE] Notification', {
+  logger?.debug?.('[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });
-  logger?.info('[NobleBLE] Packet assembled', {
+  logger?.debug?.('[NobleBLE] Packet assembled', {
     deviceId,
     totalPackets: packetState.packetCount,
     expectedLength: packetState.bufferLength,
     actualLength: packetState.buffer.length - COMMON_HEADER_SIZE,
   });

Also applies to: 172-177


705-709: Normalize force reconnect error message

Avoid error.message on a non-Error.

-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));

755-758: Normalize fresh peripheral connect error

Handle unknown and preserve message.

-        if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+        if (error) {
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));

1094-1113: Chunk write logs leak payloads; also normalize write errors and consider with-response

  • Don’t log full chunk hex. Keep a short preview and sizes.
  • Normalize callback error and consider with-response for reliability.
-  logger?.info('[NobleBLE] Splitting into chunks:', {
+  logger?.info('[NobleBLE] Splitting into chunks:', {
     deviceId,
     totalChunks: chunks.length,
     chunkSize: BLE_PACKET_SIZE,
     totalDataLength: buffer.length,
   });
@@
-  const writeChunk = (chunk: Buffer, chunkIndex: number): Promise<void> =>
+  const writeChunk = (chunk: Buffer, chunkIndex: number): Promise<void> =>
     new Promise<void>((resolve, reject) => {
-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
-        withResponse: false,
+        chunkPreview: `${preview}...`,
+        withResponse: false,
       });
 
-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
           logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           return;
         }
         resolve();
       });
     });

1164-1169: Guard subscribe against in-flight unsubscribe

If state is 'unsubscribing', wait until idle before proceeding to avoid races.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    // Wait for unsubscribe to settle
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1212-1230: Rebuild subscription: normalize subscribe/unsubscribe errors; avoid async Promise executor

  • Use unknown for errors and normalize.
  • Avoid async executor anti-pattern; use straightforward async/await.
-  async function rebuildAppSubscription(
+  async function rebuildAppSubscription(
     deviceId: string,
     notifyCharacteristic: Characteristic
   ): Promise<void> {
     notifyCharacteristic.removeAllListeners('data');
-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) =>
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) {
+          logger?.error('[NobleBLE] Unsubscribe before resubscribe failed:', error);
+          return reject(new Error(toErrorMessage(error)));
+        }
+        resolve();
+      })
+    );
+    await new Promise<void>((resolve, reject) =>
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      })
+    );

1407-1407: IPC write log reports hex chars, not bytes

Log byte length for clarity.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ceb8577 and 7608d5b.

📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (21 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (4)
packages/hd-transport-electron/src/noble-ble-handler.ts (4)

25-25: LGTM: pairing probe integration is clear and scoped

Importing and using runPairingProbe aligns with the PR goal and keeps platform-specific logic contained.


945-947: LGTM: short settle delay after reusing an existing connection

The 300ms wait helps stabilize the link before discovery. Reasonable trade-off.


1361-1366: Nice: safeLog used for IPC paths

Good use of safeLog to avoid logger null checks and keep structured logs.


1027-1045: Error path on hex conversion is solid

You catch Buffer.from errors and wrap them in a TypedError. Good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/hd-transport-electron/src/noble-ble-handler.ts (1)

423-429: Clean up discover listener on timeout; remove no-op pre-remove.

You add the listener but don’t remove it on timeout, which leaks. Also, calling removeListener(onDiscover) before adding does nothing.

-    const timeout = setTimeout(() => {
+    const timeout = setTimeout(() => {
       if (noble) {
         noble.stopScanning();
+        // Remove the temporary discover handler to avoid leaks
+        noble.removeListener('discover', onDiscover);
       }
       logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
       resolve(null);
     }, FAST_SCAN_TIMEOUT);
@@
-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    if (noble) noble.on('discover', onDiscover);

Also applies to: 452-456

♻️ Duplicate comments (17)
packages/connect-examples/expo-example/package.json (1)

3-3: Verify alpha.4 is actually published before merging.

The example pins four @onekeyfe packages to 1.1.10-alpha.4. Sanity-check these exist in the registry to keep the example installable.

#!/bin/bash
# Verify the exact versions exist in the npm registry
set -euo pipefail
for pkg in @onekeyfe/hd-ble-sdk @onekeyfe/hd-common-connect-sdk @onekeyfe/hd-core @onekeyfe/hd-web-sdk; do
  echo "Checking $pkg@1.1.10-alpha.4"
  npm view "$pkg@1.1.10-alpha.4" version
done

Also applies to: 22-25

packages/hd-transport-electron/src/noble-ble-handler.ts (16)

596-676: Normalize discovery callback errors (service/characteristic) to avoid crashes.

Don’t assume Error; log and reject with safe strings.

-    peripheral.discoverServices(
+    peripheral.discoverServices(
       ONEKEY_SERVICE_UUIDS,
-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
           return;
         }
@@
-        service.discoverCharacteristics(
+        service.discoverCharacteristics(
           [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
               return;
             }

1324-1341: Unsubscribe: normalize error typing and message.

Treat callback error as unknown and keep state machine consistent.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }
@@
       // 🔒 Clear operation state
       subscriptionOperations.set(deviceId, 'idle');
       resolve();
     });
   });

21-21: Type imports from @stoprocent/noble may not exist; import types from 'noble' instead.

Many forks don't ship d.ts. Keep the runtime require on @stoprocent/noble but import types from 'noble' (or add a local shim).

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

If you prefer to keep @stoprocent/noble for types, add a local module declaration that re-exports types from 'noble'.


27-27: Typo in comment.

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

67-81: Broaden pairing error detection and avoid unsafe .message access.

Handle unknown/string errors and centralize normalization.

-function isPairingError(error: Error): boolean {
+function isPairingError(error: unknown): boolean {
   const keywords = [
     'authentication',
     'pairing',
     'bonding',
     'insufficient',
     'security',
     'authorization',
     'permission',
     'access denied',
   ];
-  const errorMessage = error.message.toLowerCase();
+  const errorMessage = toErrorMessage(error).toLowerCase();
   return keywords.some(k => errorMessage.includes(k));
 }

Add this helper near the imports:

function toErrorMessage(err: unknown): string {
  if (typeof err === 'string') return err;
  if (err && typeof err === 'object' && 'message' in (err as any) && typeof (err as any).message === 'string') {
    return (err as any).message as string;
  }
  try { return JSON.stringify(err); } catch { return String(err); }
}

115-120: Reduce notification log noise; use debug level.

Per-packet info logs are expensive and chatty.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

704-709: Force reconnect: don’t use error.message directly.

Coerce unknown to string.

-    peripheral.connect((error: Error | undefined) => {
+    peripheral.connect((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));

755-758: Fresh peripheral connect: normalize error before using it.

-      freshPeripheral.connect((error: Error | undefined) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));

971-976: Initial connect: treat callback error as unknown and normalize.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }

1053-1061: Single write: consider with-response for control packets; fix logging.

With-response detects link issues sooner. If you keep withoutResponse, reflect it in logs.

-  if (buffer.length <= BLE_PACKET_SIZE) {
-    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length });
+  if (buffer.length <= BLE_PACKET_SIZE) {
+    const withoutResponse = false; // safer for small control packets; set true if you prefer throughput
+    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length, withoutResponse });
     await wait(5);
     return new Promise((resolve, reject) => {
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
           logger?.error('[NobleBLE] Write single failed:', error);
-          if (isPairingError(error)) {
+          if (isPairingError(error)) {
             reject(
               ERRORS.TypedError(
                 HardwareErrorCode.BleWriteCharacteristicError,
-                `Pairing required for write operation: ${error.message}`
+                `Pairing required for write operation: ${toErrorMessage(error)}`
               )
             );
           } else {
-            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           }
           return;
         }
 
         logger?.info('[NobleBLE] Write single ok', { deviceId });

1104-1113: Don’t log full chunk payloads; trim to a short preview.

Prevents leaking data and noisy logs.

-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
-        withResponse: false,
+        chunkPreview: `${preview}...`,
+        withoutResponse: true,
       });
@@
-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
           logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           return;
         }
         resolve();
       });

1164-1169: Guard subscribe against in-flight unsubscribe to avoid races.

Wait until state returns to idle before proceeding.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    // Wait until unsubscribe completes
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1212-1230: Rebuild subscription: normalize unsubscribe/subscribe callback errors.

Avoid assuming Error.

   async function rebuildAppSubscription(
     deviceId: string,
     notifyCharacteristic: Characteristic
   ): Promise<void> {
     notifyCharacteristic.removeAllListeners('data');
-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) {
+          logger?.error('[NobleBLE] Unsubscribe before resubscribe failed:', error);
+          return reject(new Error(toErrorMessage(error)));
+        }
+        resolve();
+      });
+    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });

1277-1290: Non-Windows probe: comment contradicts code; fix arg and normalize error.

If “no response is expected,” pass withoutResponse=true and log safely.

-      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
-      // This is a dummy write operation that doesn't expect a response.
+      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
+      // This is a dummy write operation; no response is expected.
       logger?.info(`[NobleBLE] Performing probe write to trigger pairing on ${process.platform}`);
       const probeData = Buffer.from('01', 'hex'); // A single byte is sufficient
       await new Promise<void>((resolve, reject) => {
-        characteristics.write.write(probeData, false, error => {
+        characteristics.write.write(probeData, true, (error: unknown) => {
           if (error) {
             // We can often ignore errors here, as the goal is just to trigger the dialog.
             // However, log it for debugging purposes.
-            logger?.error('[NobleBLE] Probe write failed (this may be expected):', error.message);
+            logger?.debug('[NobleBLE] Probe write failed (this may be expected):', toErrorMessage(error));
           }
           resolve(); // Resolve regardless of error
         });
       });

1407-1408: IPC write log: report bytes, not hex string length.

hexData.length counts nibbles.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

97-98: Connection timeout likely mismatches the 30s Windows pairing window; make it configurable.

Expose via env with a sane default (30s). If you truly want 3s (per discussion), set the env in your build/run.

-const CONNECTION_TIMEOUT = 15000; // 15 seconds for device connection
+const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 30000);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7608d5b and 9a99b92.

📒 Files selected for processing (4)
  • packages/connect-examples/electron-example/electron-builder.config.js (1 hunks)
  • packages/connect-examples/electron-example/webpack.config.ts (1 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (21 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (3)
packages/connect-examples/electron-example/electron-builder.config.js (1)

14-18: Good call unpacking native BLE modules; add a note to keep these in sync with externals.

asarUnpack entries look correct for dynamically loaded .node binaries. Ensure these stay aligned with webpack externals and actual runtime deps to avoid loading failures at startup.

If you want, I can add a quick checklist to CI to assert those paths exist post-install on all OS targets.

packages/connect-examples/electron-example/webpack.config.ts (1)

51-53: Externals update looks right; abandonware entries are gone.

Nice cleanup. This matches the migration to @stoprocent/* and avoids bundling native layers.

packages/connect-examples/expo-example/package.json (1)

10-10: Nice: add memory headroom for electron-web build.

The 4GB heap avoids OOMs in larger trees.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/hd-transport-electron/src/noble-ble-handler.ts (2)

1325-1341: Unsubscribe: normalize error type

Do not assume Error in callback.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

1348-1354: Leftover debug log

Drop the bare console.log('NOBLE_VERSION_771') or tie it to a debug flag.

-    console.log('NOBLE_VERSION_771');
+    // console.log('NOBLE_VERSION_771'); // enable for local diagnostics only
♻️ Duplicate comments (23)
packages/connect-examples/electron-example/package.json (1)

23-31: Linux runtime risk: add @stoprocent/bluetooth-hci-socket explicitly

On many Linux setups noble requires the HCI socket native addon. It often won’t arrive via optional deps in app packaging. Add it explicitly to avoid “module not found” at runtime.

   "dependencies": {
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",

Also ensure electron-builder asarUnpack includes both noble and the HCI socket folders (looks handled elsewhere—please re-verify in this example app).

packages/hd-transport-electron/package.json (3)

24-26: Guide Linux integrators: declare HCI socket as optional peer

Make the expected native piece visible without forcing it on macOS/Windows. Add as an optional peer.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

16-21: Native addon ABI: plan for Electron rebuilds

noble/HCI socket are native. Ensure rebuild against your Electron ABI. Consider providing a postinstall or docs in this package (or in the example app) to run electron-rebuild.

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
     "lint": "eslint .",
-    "lint:fix": "eslint . --fix"
+    "lint:fix": "eslint . --fix",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket || echo \"skip electron-rebuild\""
   },

If you prefer not to run rebuilds from a library, ship a clear README note and a check script in the example app instead.


28-32: Allow patch updates for noble

Pinning blocks security/bugfix patches. Prefer a caret.

-    "@stoprocent/noble": "2.3.4"
+    "@stoprocent/noble": "^2.3.4"
packages/hd-transport-electron/src/utils/blePairing.ts (4)

11-15: Make write-without-response configurable and log it

Hardcoding withoutResponse can hurt pairing on Windows. Expose it as an option and log the mode.

 export interface Step3Options {
   intervalMs?: number; // default 3000
   maxCycles?: number; // default 10
   initDataHex?: string; // hex string to write; default OneKey init
+  withoutResponse?: boolean; // default true
 }
@@
-  const maxCycles = options.maxCycles ?? 10;
-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const maxCycles = options.maxCycles ?? 10;
+  const initDataHex = options.initDataHex ?? getDefaultInitHex();
+  const withoutResponse = options.withoutResponse ?? true;
@@
-          writeCharacteristic.write(buffer, true, (writeError?: Error) => {
-            logger?.info(
-              `[Pairing] Listeners after attach: ${notifyCharacteristic.listenerCount('data')}`
-            );
+          writeCharacteristic.write(buffer, withoutResponse, (writeError?: Error) => {
+            logger?.info(
+              `[Pairing] Listeners after attach: ${notifyCharacteristic.listenerCount('data')}, withoutResponse: ${withoutResponse}`
+            );

Also applies to: 33-35, 97-101


40-41: Use portable timer type (Electron typings)

NodeJS.Timeout clashes with DOM typings. Use ReturnType.

-    let intervalId: NodeJS.Timeout | null = null;
+    let intervalId: ReturnType<typeof setInterval> | null = null;

44-51: Clean up subscription too (avoid residual subscriptions)

Remove 'data' listeners and unsubscribe to leave a clean state after the probe.

   const cleanup = () => {
     if (intervalId) clearInterval(intervalId);
-    // pairing阶段结束前,先移除一次性侦听器,避免泄露
-    logger?.info(
-      `[Pairing] Cleaning up ${notifyCharacteristic.listenerCount('data')} listeners before cycle`
-    );
+    // Remove one-shot listeners before leaving pairing to avoid leaks
+    logger?.info(
+      `[Pairing] Cleaning up ${notifyCharacteristic.listenerCount('data')} listeners before cycle`
+    );
     notifyCharacteristic.removeAllListeners('data');
+    try {
+      // Best-effort unsubscribe; ignore errors
+      // @ts-ignore subscribe API guarantees callback form
+      notifyCharacteristic.unsubscribe(() => {});
+    } catch (_) { /* noop */ }
   };

46-47: Unify comment language and phrasing

Use concise English for consistency and readability.

-      // pairing阶段结束前,先移除一次性侦听器,避免泄露
+      // Remove one-shot listeners before finishing pairing to avoid leaks
@@
-      // 清理旧监听,确保不会累积
+      // Clean previous listeners to avoid accumulation
@@
-    // 立即开始第一轮,然后每 intervalMs 一轮
+    // Start immediately, then repeat every intervalMs

Also applies to: 75-80, 128-131

packages/hd-transport-electron/src/noble-ble-handler.ts (15)

970-999: Connect callback: normalize error and avoid 3s timeout flaps

Use the configurable timeout (see earlier) and normalize error.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
-        logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        logger?.error('[NobleBLE] Connection failed:', error);
+        const msg = (typeof error === 'object' && error && 'message' in (error as any))
+          ? (error as any).message
+          : String(error);
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, msg));
         return;
       }

21-21: Type imports from @stoprocent/noble may fail

Some noble forks ship no types. Import types from 'noble' or add a local d.ts shim that re-exports them. Keep runtime require('@stoprocent/noble').

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

If you prefer keeping @stoprocent/noble for types, declare a module shim that re-exports 'noble' types.


97-98: 3s connect timeout is too short; align with pairing window

Windows pairing can run up to 30s. A 3s timeout will flap. Make it configurable and default to 30s.

-const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 30000);

115-119: Turn chatty logs to debug

Per-notification and per-packet logs at info are noisy. Use debug to reduce log volume and CPU.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
@@
-      logger?.info('[NobleBLE] Packet assembled', {
+      safeLog(logger, 'debug', '[NobleBLE] Packet assembled', {

Also applies to: 172-177


601-676: Discovery callbacks: treat errors as unknown and normalize

Avoid .message on strings. Normalize before logging and rejecting.

-    peripheral.discoverServices(
+    peripheral.discoverServices(
       ONEKEY_SERVICE_UUIDS,
-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
-          logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          logger?.error('[NobleBLE] Service discovery failed:', error);
+          const msg = (typeof error === 'object' && error && 'message' in (error as any))
+            ? (error as any).message
+            : String(error);
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, msg));
           return;
         }
@@
-        service.discoverCharacteristics(
+        service.discoverCharacteristics(
           [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
-              logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              logger?.error('[NobleBLE] Characteristic discovery failed:', error);
+              const msg = (typeof error === 'object' && error && 'message' in (error as any))
+                ? (error as any).message
+                : String(error);
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, msg));
               return;
             }

If you already have a toErrorMessage helper, prefer importing and using it.


704-709: Normalize error in force reconnect

Avoid assuming Error.

-        logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        logger?.error('[NobleBLE] Force reconnect failed:', error);
+        const msg = (typeof error === 'object' && error && 'message' in (error as any))
+          ? (error as any).message
+          : String(error);
+        reject(new Error(`Force reconnect failed: ${msg}`));

754-758: Normalize error in fresh peripheral connect

Same here.

-        if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+        if (error) {
+          const msg = (typeof error === 'object' && error && 'message' in (error as any))
+            ? (error as any).message
+            : String(error);
+          reject(new Error(`Fresh peripheral connection failed: ${msg}`));

1055-1075: Single write: prefer with-response; normalize error; surface pairing cleanly

With-response (false) detects link issues early. If you keep withoutResponse, add a comment why.

-    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length });
+    const withoutResponse = false; // safer for small control/control-like packets
+    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length, withoutResponse });
@@
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
-          logger?.error('[NobleBLE] Write single failed:', error);
-          if (isPairingError(error)) {
+          logger?.error('[NobleBLE] Write single failed:', error);
+          const msg = (typeof error === 'object' && error && 'message' in (error as any))
+            ? (error as any).message
+            : String(error);
+          if (isPairingError(error as any)) {
             reject(
               ERRORS.TypedError(
                 HardwareErrorCode.BleWriteCharacteristicError,
-                `Pairing required for write operation: ${error.message}`
+                `Pairing required for write operation: ${msg}`
               )
             );
           } else {
-            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, msg));
           }
           return;
         }

1094-1113: Chunk write: do not log full payload; normalize errors

Trim logs and avoid leaking payloads.

-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
+        chunkPreview: `${preview}...`,
         withResponse: false,
       });
@@
-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
-          logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
+          const msg = (typeof error === 'object' && error && 'message' in (error as any))
+            ? (error as any).message
+            : String(error);
+          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, msg));
           return;
         }

Also applies to: 1112-1116


1164-1169: Guard subscribe against in‑flight unsubscribe

If an unsubscribe is running, wait until idle to avoid races.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
@@
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    // Wait until unsubscribe completes
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1212-1230: Normalize subscribe/unsubscribe callback errors

Treat callback arg as unknown and coerce to Error string safely.

   async function rebuildAppSubscription(
     deviceId: string,
     notifyCharacteristic: Characteristic
   ): Promise<void> {
     notifyCharacteristic.removeAllListeners('data');
-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) {
+          const msg = (typeof error === 'object' && error && 'message' in (error as any))
+            ? (error as any).message
+            : String(error);
+          return reject(new Error(msg));
+        }
+        resolve();
+      });
+    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) {
+          const msg = (typeof error === 'object' && error && 'message' in (error as any))
+            ? (error as any).message
+            : String(error);
+          return reject(new Error(msg));
+        }
+        resolve();
+      });
+    });

1277-1293: Probe write (non‑Windows): comment contradicts code; normalize error

The comment says “doesn't expect a response” but you pass false (with response). Either pass true or fix the comment. Also handle unknown error.

-      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
-      // This is a dummy write operation that doesn't expect a response.
+      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
+      // This is a dummy write operation; no response is expected.
       logger?.info(`[NobleBLE] Performing probe write to trigger pairing on ${process.platform}`);
       const probeData = Buffer.from('01', 'hex'); // A single byte is sufficient
       await new Promise<void>((resolve, reject) => {
-        characteristics.write.write(probeData, false, error => {
-          if (error) {
+        characteristics.write.write(probeData, true, (error: unknown) => {
+          if (error) {
             // We can often ignore errors here, as the goal is just to trigger the dialog.
             // However, log it for debugging purposes.
-            logger?.error('[NobleBLE] Probe write failed (this may be expected):', error.message);
+            const msg = (typeof error === 'object' && error && 'message' in (error as any))
+              ? (error as any).message
+              : String(error);
+            logger?.debug('[NobleBLE] Probe write failed (this may be expected):', msg);
           }
           resolve(); // Resolve regardless of error
         });
       });

1407-1408: IPC write log: report bytes, not hex chars

hexData.length counts hex chars. Divide by 2 for bytes.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

67-81: Make isPairingError safer and reusable

Accept unknown and normalize once. Avoid .message on strings. If you have a shared toErrorMessage helper, reuse it.

-function isPairingError(error: Error): boolean {
+function isPairingError(error: unknown): boolean {
   const keywords = [
     'authentication',
     'pairing',
     'bonding',
     'insufficient',
     'security',
     'authorization',
     'permission',
     'access denied',
   ];
-  const errorMessage = error.message.toLowerCase();
+  const errorMessage = (typeof error === 'object' && error && 'message' in (error as any))
+    ? String((error as any).message).toLowerCase()
+    : String(error).toLowerCase();
   return keywords.some(k => errorMessage.includes(k));
}

If you prefer, add a small toErrorMessage(err: unknown): string helper near the imports and use it across this file.


27-27: Typo in comment

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9a99b92 and 1386e80.

📒 Files selected for processing (5)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (21 hunks)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
  • packages/hd-transport/src/index.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/hd-transport/src/index.ts (1)

13-15: Re‑exporting protobuf may re‑run protobuf configuration; ensure idempotency

This index already sets protobuf.util.Long = Long and calls protobuf.configure() (Lines 16–17). Re‑exporting ./serialization/protobuf may import a module that does the same, leading to duplicate configuration and subtle runtime issues. Guard the configuration in serialization/protobuf so it only runs once, or refactor that module to export helpers without side effects.

Suggested guard inside serialization/protobuf.ts:

let configured = false;
export function configureProtobufOnce(pb: typeof import('protobufjs/light'), LongImpl: typeof import('long')) {
  if (configured) return;
  pb.util.Long = LongImpl as any;
  pb.configure();
  configured = true;
}

Then ensure either:

  • Only this index configures protobuf (keep Lines 16–17) and serialization/protobuf exports types/utilities without side effects; or
  • Remove Lines 16–17 here and instead call configureProtobufOnce from a single, well-known entry point.

Also, widening the public API surface via these exports may warrant at least a minor bump when leaving prerelease.

To verify duplication quickly:

#!/bin/bash
# Look for protobuf.configure() or Long assignment inside serialization modules
fd -t f 'protobuf.ts' packages | xargs rg -nP -C2 'protobuf\.(util\.Long|configure\s*\()'
# Look for existing idempotent flags
rg -nP '__hdTransportConfigured|configure(Protobuf)?Once|configured\s*=' packages/hd-transport/src/serialization
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1386e80 and f7a7818.

📒 Files selected for processing (2)
  • packages/connect-examples/electron-example/src/index.ts (1 hunks)
  • packages/hd-transport/src/index.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
packages/connect-examples/electron-example/src/index.ts (1)

211-212: Removing BLE init from createMainWindow prevents double‑init on reloads.

Good move. This aligns with the earlier recommendation to avoid per‑reload init and native load crashes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f7a7818 and af655c4.

📒 Files selected for processing (1)
  • packages/connect-examples/electron-example/src/index.ts (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (2)
packages/connect-examples/electron-example/src/index.ts (2)

287-294: Solid: try/catch + structured logs around Noble init.

Nice defensive wrapper and clear logs.


287-294: Packaging already unpacks native modules

I checked packages/connect-examples/electron-example/electron-builder.config.js and confirmed it includes:

• asarUnpack:

  • node_modules/@stoprocent/noble/**
  • node_modules/@stoprocent/bluetooth-hci-socket/**

This satisfies the requirement to unpack those native binaries at runtime. No further changes are needed here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/hd-transport-electron/src/noble-ble-handler.ts (3)

420-456: Targeted scan listener management is wrong; leaks possible.

You call removeListener('discover', onDiscover) before adding it, and you never remove the listener on timeout. Clean up in all paths.

-    const onDiscover = (peripheral: Peripheral) => {
+    const onDiscover = (peripheral: Peripheral) => {
       if (peripheral.id === targetDeviceId) {
-        clearTimeout(timeout);
-        if (noble) {
-          noble.stopScanning();
-          noble.removeListener('discover', onDiscover);
-        }
+        cleanup();
         // Cache the found device
         discoveredDevices.set(peripheral.id, peripheral);
         logger?.info('[NobleBLE] OneKey device found during targeted scan:', {
           id: peripheral.id,
           name: peripheral.advertisement?.localName || 'Unknown',
         });
         resolve(peripheral);
       }
     };
-
-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    const cleanup = () => {
+      clearTimeout(timeout);
+      if (noble) {
+        noble.removeListener('discover', onDiscover);
+        noble.stopScanning();
+      }
+    };
+    if (noble) noble.on('discover', onDiscover);
@@
-    const timeout = setTimeout(() => {
-      if (noble) {
-        noble.stopScanning();
-      }
-      logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
-      resolve(null);
-    }, FAST_SCAN_TIMEOUT);
+    const timeout = setTimeout(() => {
+      logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
+      cleanup();
+      resolve(null);
+    }, FAST_SCAN_TIMEOUT);
@@
-      if (error) {
-        clearTimeout(timeout);
-        if (noble) {
-          noble.removeListener('discover', onDiscover);
-        }
+      if (error) {
+        cleanup();
         logger?.error('[NobleBLE] Failed to start targeted scan:', error);
         reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
         return;
       }

744-763: Fresh-scan connect path misses a disconnect listener.

You set connectedDevices for freshPeripheral but never call setupDisconnectListener, so disconnects won’t be handled.

Option A (preferred): pass webContents into connectAndDiscoverWithFreshScan and set the listener after connecting.

-async function connectAndDiscoverWithFreshScan(deviceId: string): Promise<CharacteristicPair> {
+async function connectAndDiscoverWithFreshScan(
+  deviceId: string,
+  webContents: WebContents
+): Promise<CharacteristicPair> {
@@
-      freshPeripheral.connect((error: unknown) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
           reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));
         } else {
           connectedDevices.set(deviceId, freshPeripheral);
+          setupDisconnectListener(freshPeripheral, deviceId, webContents);
           resolve();
         }
       });

And update callers:

-const characteristics = await connectAndDiscoverWithFreshScan(deviceId);
+const characteristics = await connectAndDiscoverWithFreshScan(deviceId, webContents);

Also applies to: 980-984


905-917: Avoid recursion while waiting for subscription operations.

Repeated recursion risks stack growth. Poll in a loop instead.

-        await wait(100);
-        // Retry connection after waiting
-        return connectDevice(deviceId, webContents);
+        while (subscriptionOperations.get(deviceId) !== 'idle') {
+          // eslint-disable-next-line no-await-in-loop
+          await wait(50);
+        }
♻️ Duplicate comments (19)
packages/hd-transport-electron/package.json (3)

23-26: Add optional peer for Linux HCI backend.

Declare @stoprocent/bluetooth-hci-socket as an optional peer to guide Linux users without forcing it on macOS/Windows.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

16-21: Ensure native addons rebuild for Electron.

@noble’s stack can include native modules; rebuild against Electron’s ABI to avoid runtime load errors. Add electron-rebuild and a postinstall hook.

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
     "lint": "eslint .",
-    "lint:fix": "eslint . --fix"
+    "lint:fix": "eslint . --fix",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket || true"
   },
@@
   "devDependencies": {
     "@types/web-bluetooth": "^0.0.17",
     "electron": "^25.0.0",
+    "electron-rebuild": "^3.6.0",
     "typescript": "^5.3.3"
   }

Also applies to: 34-38


31-31: Relax noble to caret to pick up patch fixes.

Allow non-breaking updates automatically.

-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
packages/hd-transport-electron/src/noble-ble-handler.ts (16)

684-706: Force reconnect: normalize errors on connect failure.

Avoid .message on non-Error; surface clear messages.

-  await new Promise<void>((resolve, reject) => {
-    peripheral.connect((error: Error | undefined) => {
+  await new Promise<void>((resolve, reject) => {
+    peripheral.connect((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));
       } else {

Also applies to: 708-708


753-763: Fresh scan connect: normalize error type and message.

Same issue here.

-      freshPeripheral.connect((error: Error | undefined) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));
         } else {

970-999: Connect callback: normalize error type and message.

Treat error as unknown and avoid property access on non-Errors.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }
@@
-        const characteristics = await connectAndDiscoverWithFreshScan(deviceId);
+        const characteristics = await connectAndDiscoverWithFreshScan(deviceId, webContents);

1325-1341: Unsubscribe: treat callback error as unknown.

Normalize the message; avoid property access on strings.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

21-21: Import types from 'noble' or add a shim; @stoprocent/noble may lack d.ts.

Avoid broken type resolution when the fork ships runtime only.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

If you prefer sticking to @stoprocent/noble everywhere, add a local .d.ts that re-exports types from 'noble'.


27-27: Fix typo in comment.

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

67-81: Make isPairingError robust to non-Error throws; add toErrorMessage helper.

Callbacks sometimes pass strings. Accessing .message will crash.

-function isPairingError(error: Error): boolean {
+function isPairingError(error: unknown): boolean {
-  const keywords = [
+  const keywords = [
     'authentication',
     'pairing',
     'bonding',
     'insufficient',
     'security',
     'authorization',
     'permission',
     'access denied',
   ];
-  const errorMessage = error.message.toLowerCase();
+  const errorMessage = toErrorMessage(error).toLowerCase();
   return keywords.some(k => errorMessage.includes(k));
 }

Add this helper near the imports:

function toErrorMessage(err: unknown): string {
  if (typeof err === 'string') return err;
  if (err && typeof err === 'object' && 'message' in (err as any) && typeof (err as any).message === 'string') {
    return (err as any).message as string;
  }
  try { return JSON.stringify(err); } catch { return String(err); }
}

97-97: 3s connect timeout is too aggressive; align with 30s probe or make configurable.

Windows pairing probe runs up to 30s. A 3s connect timeout will fire early.

-const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 30000); // 30s default; override via env

115-120: Reduce log noise on hot paths (notifications/assembly).

Use debug for per-packet logs. Keep info for summaries/errors.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });
@@
-    logger?.info('[NobleBLE] Initialized new packet state for device:', deviceId);
+    safeLog(logger, 'debug', 'Initialized new packet state', { deviceId });
@@
-      logger?.info('[NobleBLE] Packet assembled', {
+      safeLog(logger, 'debug', 'Packet assembled', {
         deviceId,
         totalPackets: packetState.packetCount,
         expectedLength: packetState.bufferLength,
         actualLength: packetState.buffer.length - COMMON_HEADER_SIZE,
       });

Also applies to: 126-127, 172-178


601-676: Normalize discovery callback errors; don’t assume Error.

Type the callback error as unknown and use toErrorMessage for logging and TypedError.

-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
-          logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          logger?.error('[NobleBLE] Service discovery failed:', error);
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
           return;
         }
@@
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
               return;
             }

1055-1070: Single write: use with-response for reliability and normalize errors.

With-response detects link issues earlier; also guard error typing.

-    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length });
+    const withoutResponse = false; // safer for control packets
+    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length, withoutResponse });
@@
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
-          logger?.error('[NobleBLE] Write single failed:', error);
-          if (isPairingError(error)) {
+          logger?.error('[NobleBLE] Write single failed:', error);
+          if (isPairingError(error)) {
             reject(
               ERRORS.TypedError(
                 HardwareErrorCode.BleWriteCharacteristicError,
-                `Pairing required for write operation: ${error.message}`
+                `Pairing required for write operation: ${toErrorMessage(error)}`
               )
             );
           } else {
-            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+            reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           }
           return;
         }
 
-        logger?.info('[NobleBLE] Write single ok', { deviceId });
+        logger?.info('[NobleBLE] Write single ok', { deviceId });

1094-1113: Chunked writes: redact payloads, use with-response toggle, normalize errors.

Do not log full data. Normalize error type.

-  logger?.info('[NobleBLE] Splitting into chunks:', {
+  logger?.info('[NobleBLE] Splitting into chunks:', {
     deviceId,
     totalChunks: chunks.length,
     chunkSize: BLE_PACKET_SIZE,
     totalDataLength: buffer.length,
   });
@@
-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
-        withResponse: false,
+        chunkPreview: `${preview}...`,
+        withoutResponse: true,
       });
@@
-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
           logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           return;
         }
         resolve();
       });

If you want higher reliability over throughput, set withoutResponse to false and keep a slightly larger CHUNK_WRITE_DELAY.

Also applies to: 1112-1116


1164-1169: Guard against concurrent ‘unsubscribing’ state before subscribing.

Avoid race with in-flight unsubscribe.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1212-1231: Subscribe/unsubscribe: treat callback errors as unknown and normalize.

Prevent crashes on string errors and keep logs consistent.

-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) {
+          logger?.error('[NobleBLE] Unsubscribe before resubscribe failed:', error);
+          return reject(new Error(toErrorMessage(error)));
+        }
+        resolve();
+      });
+    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });
@@
-    notifyCharacteristic.on('data', (data: Buffer) => {
+    notifyCharacteristic.on('data', (data: Buffer) => {
       const result = processNotificationData(deviceId, data);
       if (result.error) {
         logger?.error('[NobleBLE] Packet processing error:', result.error);
         return;
       }
       if (result.isComplete && result.completePacket) {
         const appCb = notificationCallbacks.get(deviceId);
         if (appCb) appCb(result.completePacket);
       }
     });

Also applies to: 1232-1243


1277-1293: Non-Windows probe write: comment contradicted behavior; fix and normalize error.

Pass withoutResponse=true if you don’t expect a response; also handle unknown error type.

-      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
-      // This is a dummy write operation that doesn't expect a response.
+      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
+      // This is a dummy write operation; no response is expected.
       logger?.info(`[NobleBLE] Performing probe write to trigger pairing on ${process.platform}`);
       const probeData = Buffer.from('01', 'hex'); // A single byte is sufficient
       await new Promise<void>((resolve, reject) => {
-        characteristics.write.write(probeData, false, error => {
+        characteristics.write.write(probeData, true, (error: unknown) => {
           if (error) {
             // We can often ignore errors here, as the goal is just to trigger the dialog.
             // However, log it for debugging purposes.
-            logger?.error('[NobleBLE] Probe write failed (this may be expected):', error.message);
+            logger?.debug('[NobleBLE] Probe write failed (this may be expected):', toErrorMessage(error));
           }
           resolve(); // Resolve regardless of error
         });
       });

1414-1415: Log byte length, not hex string length.

hexData.length counts nibbles.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between af655c4 and ecca646.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (22 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (2)
packages/hd-transport-electron/src/noble-ble-handler.ts (2)

25-25: LGTM: pairing probe import.

Importing runPairingProbe here keeps pairing logic modular. No issues.


271-271: Runtime require of @stoprocent/noble: OK, but ensure dependency and guard bundlers.

Good for Electron main to avoid bundling issues. Keep it in dependencies (done) and document that bundlers should not try to tree-shake it out.

If you see packager issues, consider:

// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
noble = require('@stoprocent/noble') as NobleModule;
// Optionally assert presence:
if (!noble) throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'Failed to load noble runtime');

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/build-example-desktop.yml (1)

40-46: Fail fast if artifacts are missing

Catch packaging issues early by failing when no .exe is produced.

       - name: Upload Windows Artifacts
         uses: actions/upload-artifact@v4
         with:
           name: win-x64-example-desktop
           path: |
             ${{ env.PROJECT_PATH }}/out/*.exe
+          if-no-files-found: error
♻️ Duplicate comments (1)
packages/connect-examples/electron-example/package.json (1)

24-32: Add the HCI socket dependency explicitly to avoid runtime errors (Linux)

Noble needs @stoprocent/bluetooth-hci-socket at runtime on Linux. It isn’t guaranteed to install transitively. Add it here to prevent “module not found”.

   "dependencies": {
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "2.2.3",
     "debug": "4.3.4",
@@
-    "node-fetch": "^2.6.7",
+    "node-fetch": "^2.6.7",
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.4"
   },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ecca646 and 2b22701.

📒 Files selected for processing (3)
  • .github/workflows/build-example-desktop.yml (1 hunks)
  • package.json (2 hunks)
  • packages/connect-examples/electron-example/package.json (2 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
.github/workflows/build-example-desktop.yml

[warning] 7-45: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (4)
package.json (2)

33-35: Version guard before publish looks good

Nice addition. This prevents mismatched versions from slipping into a publish.


54-55: Dev script addition is fine

The new dev:transport-electron script aligns with changes in hd-transport-electron.

packages/connect-examples/electron-example/package.json (2)

30-31: Double-check node-fetch necessity

Electron 28 bundles a Node runtime with fetch available in many contexts. If you don’t need Node-specific features, consider removing node-fetch to reduce surface area.

I can help scan the codebase for require('node-fetch') or import fetch call sites and suggest safe removal if unused.


24-32: Native modules are correctly unpacked
Both @stoprocent/noble and @stoprocent/bluetooth-hci-socket are listed under asarUnpack in electron-builder.config.js. BLE support will work as expected.

Copy link

socket-security bot commented Aug 25, 2025

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/connect-examples/electron-example/src/index.ts (1)

279-296: Prefer app.once('ready') to avoid accidental double init.

Using once reduces the chance of re‑registration on module reloads.

-  app.on('ready', () => {
+  app.once('ready', () => {
package.json (1)

40-41: Add CI‑safe desktop build scripts (never publish).

Provide CI variants that hard‑disable publishing. Point workflows to these.

     "build:example:desktop:mac": "cd ./packages/connect-examples/electron-example && yarn make:mac",
     "build:example:desktop:win": "cd ./packages/connect-examples/electron-example && yarn make:win",
+    "build:example:desktop:mac:ci": "cd ./packages/connect-examples/electron-example && cross-env ELECTRON_BUILDER_SKIP_PUBLISH=1 yarn make:mac --publish=never",
+    "build:example:desktop:win:ci": "cd ./packages/connect-examples/electron-example && cross-env ELECTRON_BUILDER_SKIP_PUBLISH=1 yarn make:win --publish=never",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2b22701 and 9902791.

📒 Files selected for processing (2)
  • package.json (3 hunks)
  • packages/connect-examples/electron-example/src/index.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (3)
packages/connect-examples/electron-example/src/index.ts (1)

285-291: Nice: wrap native init in try/catch with logging.

This prevents hard crashes when native modules fail to load. Good call.

package.json (2)

54-55: Useful: dev:transport-electron script.

This improves local iteration on the Electron transport. No issues.


84-84: electron-builder pin confirmed in app package; no action needed

The packages/connect-examples/electron-example/package.json already pins electron-builder to ^24.9.1, satisfying the suggestion to declare it in the app. Hoisting at the root poses no drift risk.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (7)
.github/workflows/build-example-desktop.yml (4)

6-8: Lock down GITHUB_TOKEN permissions

Add minimal permissions to the job. This also resolves the CodeQL warning.

 jobs:
   build:
+    permissions:
+      contents: read
     runs-on: windows-latest

If this job will publish, change to contents: write.


13-19: Finish Yarn cache setup for a monorepo

You enabled caching but didn’t set the dependency path. Point it at all yarn.lock files to get cache hits across workspaces.

       - name: Setup Node.js
         uses: actions/setup-node@v4
         with:
           node-version: '22'
           registry-url: 'https://registry.npmjs.org'
           cache: 'yarn'
+          cache-dependency-path: '**/yarn.lock'

20-21: Use user-level git config for long paths on hosted runners

--system often fails. Use --global.

-      - name: Enable long paths on Windows (for node-gyp)
-        run: git config --system core.longpaths true
+      - name: Enable long paths on Windows (for node-gyp)
+        run: git config --global core.longpaths true

36-39: Avoid accidental releases from CI

This script likely publishes (electron-builder --publish always). Use a CI-safe script that never publishes.

-      - name: Build Desktop Application (Windows)
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        run: yarn build:example:desktop:win
+      - name: Build Desktop Application (Windows) [no publish]
+        run: yarn build:example:desktop:win:ci

Follow-up: add the :ci script in package.json to pass --publish never (see comment in package.json).

packages/connect-examples/electron-example/package.json (3)

7-7: Fix misleading package description

Update the description to match this Electron hardware example.

-  "description": "End-to-end encrypted workspaces for teams",
+  "description": "OneKey hardware Electron example app",

18-21: Add CI-safe make scripts (no publish)

Keep make:* for local publishing, add make:*:ci that never publishes. CI should call the :ci variants.

   "scripts": {
     "build:main": "webpack --config webpack.config.ts",
     "rebuild:deps": "electron-builder install-app-deps",
-    "make:mac": "yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
-    "make:win": "yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac": "yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
+    "make:win": "yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",

Then update the workflow to use yarn build:example:desktop:win:ci which maps to make:win:ci.


24-33: Ensure HCI socket is present at runtime (Linux/Electron main)

noble needs @stoprocent/bluetooth-hci-socket on Linux. It won’t always install transitively, and you’ve externalized/asarUnpack’ed it in tooling. Add it explicitly to avoid module-not-found at runtime.

   "dependencies": {
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",
     "electron-is-dev": "^3.0.1",
     "electron-log": "^5.1.5",
     "electron-updater": "^6.2.1",
     "fs-extra": "^11.2.0",
     "node-fetch": "^2.6.7",
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.4"
   },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9902791 and 7b36c5e.

📒 Files selected for processing (2)
  • .github/workflows/build-example-desktop.yml (1 hunks)
  • packages/connect-examples/electron-example/package.json (2 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
.github/workflows/build-example-desktop.yml

[warning] 7-46: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{contents: read}}

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: build (22)
  • GitHub Check: lint (22)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/hd-transport/package.json (1)

1-20: Consider adding files whitelist and sideEffects

Keeps publishes lean and helps bundlers drop unused code.

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10-alpha.5",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "package.json",
+    "README.md",
+    "LICENSE"
+  ],
♻️ Duplicate comments (13)
packages/shared/package.json (1)

1-28: Add files whitelist and mark as side‑effect free

Repeat of a prior suggestion: ship only built artifacts and enable tree‑shaking.

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10-alpha.5",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "package.json",
+    "README.md",
+    "LICENSE"
+  ],
packages/hd-transport-lowlevel/package.json (1)

21-24: Switch internal pins to workspace protocol

Same feedback as earlier rounds: use workspace:* to simplify synchronized bumps across the monorepo.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.5"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }
packages/hd-transport-web-device/package.json (1)

23-24: Switch internal deps to workspace protocol to avoid constant version churn.

Use workspace protocol so bumps stay in sync across the monorepo.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.5"
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^"
   },
   "devDependencies": {
-    "@onekeyfe/hd-transport-electron": "1.1.10-alpha.5",
+    "@onekeyfe/hd-transport-electron": "workspace:^",
     "@types/w3c-web-usb": "^1.0.6",
     "@types/web-bluetooth": "^0.0.17"
   }

Also applies to: 27-27

packages/connect-examples/electron-example/package.json (2)

25-26: Add @stoprocent/bluetooth-hci-socket to dependencies for Linux HCI runtime.

Without it, Electron on Linux will throw “module not found” when noble loads the HCI backend.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.5",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",
     "electron-is-dev": "^3.0.1",

19-20: Split “make” scripts for CI vs. local publishing.

Prevent accidental publishing in CI by adding no-publish variants.

   "scripts": {
@@
     "rebuild:deps": "electron-builder install-app-deps",
-    "make:mac": "yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
-    "make:win": "yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac": "yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
+    "make:win": "yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",
packages/hd-transport-react-native/package.json (1)

22-23: Use workspace protocol for internal deps.

Keeps RN transport aligned with shared and base transport without manual bumps.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.5",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^",
     "@onekeyfe/react-native-ble-utils": "^0.1.4",
     "react-native-ble-plx": "3.5.0"
   }
packages/hd-ble-sdk/package.json (1)

23-26: Prefer workspace protocol for internal deps to simplify coordinated releases

This keeps monorepo bumps trivial and avoids accidental skew between packages.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.5",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.5"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }
packages/hd-transport-electron/package.json (3)

16-21: Rebuild native modules against Electron’s ABI

@noble’s stack uses native addons. Without an Electron rebuild (or prebuilds), runtime will fail due to ABI mismatch.

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
-    "lint": "eslint .",
-    "lint:fix": "eslint . --fix"
+    "lint": "eslint .",
+    "lint:fix": "eslint . --fix",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket || true"
   },

Add devDependency:

   "devDependencies": {
+    "electron-rebuild": "^3.6.0",
     "@types/web-bluetooth": "^0.0.17",
     "electron": "^25.0.0",
     "typescript": "^5.3.3"
   }

31-31: Allow patch fixes for noble

Use a caret so you get non-breaking patch updates automatically.

-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",

27-33: Declare Linux HCI socket as an optional peer for better DX

Linux users often need the HCI socket. Declare it as an optional peer to guide them without breaking macOS/Windows installs.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },
packages/hd-web-sdk/package.json (1)

24-27: Unify internal deps via workspaces to avoid skew

Switch to workspace:* for internal packages to keep versions in lockstep during development.

   "dependencies": {
     "@onekeyfe/cross-inpage-provider-core": "^0.0.17",
-    "@onekeyfe/hd-core": "1.1.10-alpha.5",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.5"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
   }
packages/hd-common-connect-sdk/package.json (2)

3-3: Ensure CHANGELOG/tag exist for 1.1.10-alpha.5

Keep release notes and tags in sync with this bump.

Run this to verify:

#!/bin/bash
set -euo pipefail

# Check for a changelog entry (repo may keep a root or per-package changelog)
rg -nP "##\\s*\\[?1\\.1\\.10-alpha\\.5\\]?" -C2 -- \
  CHANGELOG.md packages/hd-common-connect-sdk/CHANGELOG.md || true

# Confirm git tag exists locally (or in CI with fetch --tags before)
git tag -l | rg -n "1\\.1\\.10-alpha\\.5" || true

23-29: Adopt workspace protocol for internal dependencies

Keeps internal packages synced and reduces PR churn on version bumps.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.5",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport-emulator": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10-alpha.5",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.5"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-emulator": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7b36c5e and 7b2ade4.

📒 Files selected for processing (15)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (5)
packages/hd-transport/package.json (1)

3-5: Description fix LGTM

Clear, user‑facing summary. No further changes needed here.

packages/hd-ble-sdk/package.json (1)

3-3: Version bump looks consistent with the repo-wide alpha.5 sync

No issues from this file’s perspective. Ensure the publishing pipeline includes this package in the alpha.5 set.

packages/hd-transport-electron/package.json (1)

3-3: Version bump aligns with alpha.5 wave

Looks good.

packages/hd-web-sdk/package.json (1)

3-3: Alpha.5 version is consistent

No action needed here.

packages/connect-examples/expo-example/package.json (1)

3-3: Version bump matches the repo-wide alpha.5

All good.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (10)
.github/workflows/build-example-desktop-win.yml (4)

9-11: Reduce token permissions to read-only (no publish in CI).

Use contents: read. If you must publish, do it in a dedicated release workflow.

   build-win:
     runs-on: windows-latest
-    permissions:
-      contents: write
+    permissions:
+      contents: read

18-23: Tighten Yarn cache key with lockfiles.

Add cache-dependency-path so restores are correct across packages.

       - name: Setup Node.js
         uses: actions/setup-node@v4
         with:
           node-version: '22'
           cache: 'yarn'
+          cache-dependency-path: '**/yarn.lock'

24-25: Use user-level git longpaths.

--system can fail on hosted runners. Switch to --global.

-      - name: Enable long paths on Windows
-        run: git config --system core.longpaths true
+      - name: Enable long paths on Windows
+        run: git config --global core.longpaths true

33-38: Build-only in CI: remove GH_TOKEN and avoid accidental Releases.

Run a no-publish script (e.g., make:win:ci) and drop GH_TOKEN here.

       - name: Build Windows App
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
           cd packages/connect-examples/electron-example
-          yarn make:win
+          yarn make:win:ci

Add this script to packages/connect-examples/electron-example/package.json:

   "scripts": {
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",
.github/workflows/build-example-desktop-mac.yml (4)

3-5: Quote the on key to silence yamllint truthy warning.

-on:
+'on':
   workflow_call:

9-11: Limit GITHUB_TOKEN to read.

Build-only workflows should not have write perms.

   build-mac:
     runs-on: macos-latest
-    permissions:
-      contents: write
+    permissions:
+      contents: read

18-22: Enable Yarn cache and scope it to lockfiles.

       - name: Setup Node.js
         uses: actions/setup-node@v4
         with:
           node-version: '22'
+          cache: 'yarn'
+          cache-dependency-path: '**/yarn.lock'

29-35: Build-only in CI: remove GH_TOKEN and avoid publish.

Switch to a no-publish script (make:mac:ci). Drop GH_TOKEN.

       - name: Build macOS App
-        env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         run: |
           cd packages/connect-examples/electron-example
-          yarn make:mac
+          yarn make:mac:ci

Add this script to packages/connect-examples/electron-example/package.json:

   "scripts": {
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
.github/workflows/build-example-desktop.yml (2)

7-9: Add minimal permissions to the build-mac job.

Satisfies CodeQL and least-privilege.

   build-mac:
+    permissions:
+      contents: read
     uses: ./.github/workflows/build-example-desktop-mac.yml
     secrets: inherit

11-13: Add minimal permissions to the build-win job.

Mirror mac job.

   build-win:
+    permissions:
+      contents: read
     uses: ./.github/workflows/build-example-desktop-win.yml
     secrets: inherit
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 50524a1 and 7f36f32.

📒 Files selected for processing (3)
  • .github/workflows/build-example-desktop-mac.yml (1 hunks)
  • .github/workflows/build-example-desktop-win.yml (1 hunks)
  • .github/workflows/build-example-desktop.yml (1 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
.github/workflows/build-example-desktop-mac.yml

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[error] 41-41: too many blank lines (1 > 0)

(empty-lines)

🪛 GitHub Check: CodeQL
.github/workflows/build-example-desktop.yml

[warning] 8-11: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}


[warning] 12-13: Workflow does not contain permissions
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {{}}

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (1)
.github/workflows/build-example-desktop-win.yml (1)

18-23: Update Windows CI to existing scripts & confirm Node.js 22 readiness

  • Shell output shows electron-builder is pinned to ^24.9.1.
  • Node.js 22 is LTS since October 29 2024; Electron’s ecosystem moved to Node 22 as the baseline in early 2025 (github.com, electronjs.org).
  • Native modules (@stoprocent/noble, @stoprocent/bluetooth-hci-socket) use N-API via node-gyp and node-gyp-build, ensuring ABI stability on Node 22 (ABI version 131) (nodejs.org, dev.to).
  • There’s no make:win:ci script in package.json; replace it with the existing make:win.
  • Add npm run rebuild:deps before packaging to force native modules to rebuild under Node 22.

Apply this patch to .github/workflows/build-example-desktop-win.yml:

-    - name: Build Windows
-      run: npm run make:win:ci
+    - name: Rebuild native deps
+      run: npm run rebuild:deps
+
+    - name: Build Windows
+      run: npm run make:win

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (10)
.github/workflows/build-example-desktop.yml (2)

3-4: Quote the on key for linter parity

YAML linters can flag bare on. Quote it to stay quiet.

Apply:

-on:
+'on':
   workflow_dispatch:

11-12: Avoid secrets: inherit unless strictly needed

Passing all repo secrets increases blast radius. If called workflows don’t need any custom secrets, drop inherit. If they need a subset, pass only those explicitly in the called workflows.

Proposed change (if no secrets required):

   build-mac:
     uses: ./.github/workflows/build-example-desktop-mac.yml
-    secrets: inherit
   build-win:
     uses: ./.github/workflows/build-example-desktop-win.yml
-    secrets: inherit

Please confirm whether the reusable workflows require GH_TOKEN or any other secrets. If not, remove inherit here and in the called files.

Also applies to: 15-16

packages/hd-transport-electron/src/utils/blePairing.ts (8)

14-18: Make write mode configurable for Windows

Windows BLE stacks often work better with write-with-response for pairing.

 export interface Step3Options {
   intervalMs?: number; // default 3000
   maxCycles?: number; // default 10
   initDataHex?: string; // hex string to write; default OneKey init
+  withoutResponse?: boolean; // default true; false for write-with-response
 }

Then update the write call at line 105:

+  const withoutResponse = options.withoutResponse ?? true;
   // ... 
-  writeCharacteristic.write(buffer, true, (writeError: any) => {
+  writeCharacteristic.write(buffer, withoutResponse, (writeError: any) => {

68-77: Set resolved flag before reject

Ensure no further cycles run after timeout.

       if (cycles >= maxCycles) {
         if (!resolved && !responseReceived) {
           const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
           logger?.info('[Pairing] timeout', { deviceId, cycles, elapsed: totalElapsed });
+          resolved = true;
-          resolved = true;
           cleanup();
           reject(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, 'ble pairing timeout'));
         }
         return;
       }

91-118: Handle sync errors from unsubscribe

Some noble bindings throw synchronously when the peripheral is disconnected.

-      notifyCharacteristic.unsubscribe(() => {
+      try {
+        notifyCharacteristic.unsubscribe(() => {
           notifyCharacteristic.subscribe((subscribeError: any) => {
             if (subscribeError) {
               logger?.error('[Pairing] subscribe failed', {
                 deviceId,
                 cycle: cycles,
                 error: String(subscribeError ?? 'unknown error'),
               });
               scheduleNextCycle(); // Reschedule for next attempt
               return;
             }
 
             notifyCharacteristic.once('data', onData);
 
             writeCharacteristic.write(buffer, true, (writeError: any) => {
               if (writeError) {
                 logger?.error('[Pairing] write failed', {
                   deviceId,
                   cycle: cycles,
                   error: String(writeError ?? 'unknown error'),
                 });
               } else {
                 logger?.info('[Pairing] write', { deviceId, cycle: cycles });
               }
               scheduleNextCycle(); // Reschedule for next attempt
             });
           });
-      });
+        });
+      } catch (e) {
+        logger?.error('[Pairing] unsubscribe threw', { 
+          deviceId, 
+          cycle: cycles, 
+          error: String(e) 
+        });
+        scheduleNextCycle();
+      }

43-43: Use portable timer type

NodeJS.Timeout clashes with DOM typings in Electron builds.

-    let timeoutId: NodeJS.Timeout | null = null;
+    let timeoutId: ReturnType<typeof setTimeout> | null = null;

47-55: Set resolved flag before cleanup

Late write callbacks can still schedule cycles after timeout because resolved is set after cleanup.

     const onData = (data: Buffer) => {
       if (resolved || responseReceived) return;
       responseReceived = true;
       const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
       logger?.info('[Pairing] response', { deviceId, cycles, elapsed, len: data.length });
-      resolved = true;
-      cleanup();
-      resolve();
+      resolved = true;
+      cleanup();
+      resolve();
     };

63-63: Validate hex string before use

Invalid hex produces an empty Buffer and causes silent retries.

Add validation before line 63:

+    // Validate hex string
+    const isValidHex = 
+      typeof initDataHex === 'string' &&
+      initDataHex.length > 0 &&
+      (initDataHex.length % 2 === 0) &&
+      /^[0-9a-fA-F]+$/.test(initDataHex);
+    
+    if (!isValidHex) {
+      logger?.error('[Pairing] invalid initDataHex', { deviceId, hex: initDataHex });
+      reject(new Error('Invalid hex string for pairing'));
+      return;
+    }
+
     const buffer = Buffer.from(initDataHex, 'hex');

57-61: Clear timeout handle after use

Null out the handle after clearing to prevent accidental reuse.

     const cleanup = () => {
-      if (timeoutId) clearTimeout(timeoutId);
+      if (timeoutId) {
+        clearTimeout(timeoutId);
+        timeoutId = null;
+      }
       // Only remove the specific listener for this probe
       notifyCharacteristic.removeListener('data', onData);
     };

20-26: Load the default Initialize payload lazily

Building the protobuf at module load time can throw if DataManager isn't ready. It also adds startup cost even when the probe isn't used.

-// Get protobuf definitions
-const messages = DataManager.getProtobufMessages();
-const messagesRoot = parseConfigure(messages as any);
-
-// Dynamically build the Initialize message
-const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
-const DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+let DEFAULT_INIT_DATA: string | null = null;
+
+function getDefaultInitHex(): string {
+  if (DEFAULT_INIT_DATA) return DEFAULT_INIT_DATA;
+  const messages = DataManager.getProtobufMessages();
+  const messagesRoot = parseConfigure(messages as any);
+  const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
+  DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+  return DEFAULT_INIT_DATA;
+}

Then update line 37:

-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const initDataHex = options.initDataHex ?? getDefaultInitHex();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7f36f32 and a482800.

📒 Files selected for processing (2)
  • .github/workflows/build-example-desktop.yml (1 hunks)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (2)
.github/workflows/build-example-desktop.yml (2)

1-1: Good rename and split to platform-specific reusable workflows

Clear name and separation improve readability and maintenance.


11-16: Confirm reusable workflow contract

Ensure both .github/workflows/build-example-desktop-mac.yml and ...-win.yml are declared with on: workflow_call and define their own minimal permissions. Otherwise, these calls will fail.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/hd-transport-electron/src/utils/blePairing.ts (1)

1-124: Tests: cover success, timeout, unsubscribe-throw, and cancel

Add unit tests with a fake Characteristic (EventEmitter-based) to simulate:

  • subscribe error → reschedules
  • write error → reschedules
  • data event → resolves once and cleans up
  • cycles exhausted → rejects once and stops scheduling
  • unsubscribe throws → logs and reschedules
  • AbortSignal abort → rejects and cleans up

I can provide a minimal fake and timer-driven harness if helpful.

♻️ Duplicate comments (7)
packages/hd-transport-electron/src/utils/blePairing.ts (7)

1-4: Promote header to JSDoc and clarify contract

Make the header a concise JSDoc. State behavior, side effects, and terminal conditions.

Apply:

-/*
- * BLE Pairing Utilities - periodic pairing probe
- * Unsubscribe → subscribe → write every interval until response or max cycles
- */
+/**
+ * Periodic BLE pairing probe.
+ * Unsubscribe → subscribe → write until a notify response or timeout.
+ * Side effects: temporarily toggles notify subscription on the given characteristic.
+ * Resolves on first data event; rejects on timeout or when aborted.
+ */

20-27: Lazy-load Initialize payload to avoid module-load side effects

Building protobuf at import time can throw if DataManager isn’t ready and adds startup cost. Compute on first use and cache.

Apply:

-// Get protobuf definitions
-const messages = DataManager.getProtobufMessages();
-const messagesRoot = parseConfigure(messages as any);
-
-// Dynamically build the Initialize message
-const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
-const DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+let DEFAULT_INIT_DATA: string | null = null;
+function getDefaultInitHex(): string {
+  if (DEFAULT_INIT_DATA) return DEFAULT_INIT_DATA;
+  const messages = DataManager.getProtobufMessages();
+  const messagesRoot = parseConfigure(messages as any);
+  const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
+  DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+  return DEFAULT_INIT_DATA;
+}
@@
-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const initDataHex = options.initDataHex ?? getDefaultInitHex();

Also applies to: 37-37


14-18: Make write response mode configurable (Windows often prefers with-response)

Expose withoutResponse to tune reliability. Keep default behavior unchanged.

Apply:

 export interface Step3Options {
   intervalMs?: number; // default 3000
   maxCycles?: number; // default 10
   initDataHex?: string; // hex string to write; default OneKey init
+  withoutResponse?: boolean; // default true
 }
@@
-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const initDataHex = options.initDataHex ?? getDefaultInitHex();
+  const withoutResponse = options.withoutResponse ?? true;
@@
-          writeCharacteristic.write(buffer, true, (writeError: any) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError: unknown) => {
             if (writeError) {
               logger?.error('[Pairing] write failed', {
                 deviceId,
                 cycle: cycles,
-                error: String(writeError ?? 'unknown error'),
+                error: String(writeError ?? 'unknown error'),
               });
             } else {
-              logger?.info('[Pairing] write', { deviceId, cycle: cycles });
+              logger?.info('[Pairing] write', { deviceId, cycle: cycles, withoutResponse });
             }
             scheduleNextCycle(); // Reschedule for next attempt
           });

Also applies to: 37-37, 105-116


63-66: Validate initDataHex and fail fast on bad hex

Prevent silent retries with an empty/invalid buffer. Reject early with a clear error.

Apply:

-    const buffer = Buffer.from(initDataHex, 'hex');
+    const isValidHex =
+      typeof initDataHex === 'string' &&
+      initDataHex.length > 0 &&
+      (initDataHex.length % 2 === 0) &&
+      /^[0-9a-fA-F]+$/.test(initDataHex);
+    if (!isValidHex) {
+      logger?.error('[Pairing] invalid initDataHex', { deviceId, length: initDataHex?.length ?? 0 });
+      resolved = true;
+      cleanup();
+      reject(new Error('invalid initDataHex'));
+      return;
+    }
+    const buffer = Buffer.from(initDataHex, 'hex');

91-118: Guard unsubscribe against sync throws

Some noble bindings throw synchronously on unsubscribe when disconnected. Catch and reschedule cleanly.

Apply:

-      notifyCharacteristic.unsubscribe(() => {
-        notifyCharacteristic.subscribe((subscribeError: any) => {
+      try {
+        notifyCharacteristic.unsubscribe(() => {
+          notifyCharacteristic.subscribe((subscribeError: unknown) => {
             if (subscribeError) {
               logger?.error('[Pairing] subscribe failed', {
                 deviceId,
                 cycle: cycles,
                 error: String(subscribeError ?? 'unknown error'),
               });
               scheduleNextCycle(); // Reschedule for next attempt
               return;
             }
 
           notifyCharacteristic.once('data', onData);
 
-          writeCharacteristic.write(buffer, true, (writeError: any) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError: unknown) => {
             if (writeError) {
               logger?.error('[Pairing] write failed', {
                 deviceId,
                 cycle: cycles,
                 error: String(writeError ?? 'unknown error'),
               });
             } else {
-              logger?.info('[Pairing] write', { deviceId, cycle: cycles });
+              logger?.info('[Pairing] write', { deviceId, cycle: cycles, withoutResponse });
             }
             scheduleNextCycle(); // Reschedule for next attempt
           });
-        });
-      });
+          });
+        });
+      } catch (e) {
+        logger?.error('[Pairing] unsubscribe threw', { deviceId, cycle: cycles, error: String(e) });
+        scheduleNextCycle();
+      }

57-61: Unsubscribe in cleanup to leave a clean state

Avoid leaving transient subscriptions behind if the caller bails or reconfigures.

Apply:

     const cleanup = () => {
       if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; }
       // Only remove the specific listener for this probe
       notifyCharacteristic.removeListener('data', onData);
+      try {
+        notifyCharacteristic.unsubscribe(() => {});
+      } catch (_) {
+        /* noop */
+      }
     };

10-11: Normalize errors and avoid .message on non-Errors

Type callback errors as unknown and convert with a helper to keep logs sane across bindings.

Apply:

-import type { Logger } from '../types/noble-extended';
+import type { Logger } from '../types/noble-extended';
+import { toErrorMessage } from '../types/noble-extended';
@@
-        notifyCharacteristic.subscribe((subscribeError: any) => {
+        notifyCharacteristic.subscribe((subscribeError: unknown) => {
           if (subscribeError) {
             logger?.error('[Pairing] subscribe failed', {
               deviceId,
               cycle: cycles,
-              error: String(subscribeError ?? 'unknown error'),
+              error: toErrorMessage(subscribeError),
             });
             scheduleNextCycle(); // Reschedule for next attempt
             return;
           }
@@
-          writeCharacteristic.write(buffer, withoutResponse, (writeError: unknown) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError: unknown) => {
             if (writeError) {
               logger?.error('[Pairing] write failed', {
                 deviceId,
                 cycle: cycles,
-                error: String(writeError ?? 'unknown error'),
+                error: toErrorMessage(writeError),
               });
             } else {
               logger?.info('[Pairing] write', { deviceId, cycle: cycles, withoutResponse });
             }
             scheduleNextCycle(); // Reschedule for next attempt
           });
@@
-      } catch (e) {
-        logger?.error('[Pairing] unsubscribe threw', { deviceId, cycle: cycles, error: String(e) });
+      } catch (e) {
+        logger?.error('[Pairing] unsubscribe threw', { deviceId, cycle: cycles, error: toErrorMessage(e) });
         scheduleNextCycle();
       }

If you prefer not to import a helper, keep String(err ?? 'unknown error') but keep types as unknown.

Also applies to: 92-101, 105-116, 119-119

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a482800 and 464fe9d.

📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (1)
packages/hd-transport-electron/src/utils/blePairing.ts (1)

83-87: Good: self-scheduling prevents overlapping cycles

Using a setTimeout-based rescheduler and short-circuiting on resolved/received avoids reentrancy and racey overlaps. Nice.

Also applies to: 121-122

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (10)
packages/hd-transport-electron/src/utils/blePairing.ts (10)

20-26: Lazy-load DEFAULT_INIT_DATA to avoid import-time side effects

Building protobuf payloads at module load can throw and slows startup. Compute on first use and cache.

-// Get protobuf definitions
-const messages = DataManager.getProtobufMessages();
-const messagesRoot = parseConfigure(messages as any);
-
-// Dynamically build the Initialize message
-const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
-const DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+let DEFAULT_INIT_DATA: string | null = null;
+function getDefaultInitHex(): string {
+  if (DEFAULT_INIT_DATA) return DEFAULT_INIT_DATA;
+  const messages = DataManager.getProtobufMessages();
+  const messagesRoot = parseConfigure(messages as any);
+  const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
+  DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+  return DEFAULT_INIT_DATA;
+}

37-37: Use the lazy getter for default init hex

Wire the lazy loader into the options fallback.

-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const initDataHex = options.initDataHex ?? getDefaultInitHex();

14-18: Expose withoutResponse and AbortSignal in options

Make write mode explicit and let callers cancel the probe (e.g., user closes modal).

 export interface Step3Options {
   intervalMs?: number; // default 3000
   maxCycles?: number; // default 10
   initDataHex?: string; // hex string to write; default OneKey init
+  withoutResponse?: boolean; // default true
+  signal?: AbortSignal;      // optional cancel support
 }

35-37: Coerce timings; plumb signal and write mode

Prevent busy loops/immediate timeouts; read new options.

-  const intervalMs = options.intervalMs ?? 3000;
-  const maxCycles = options.maxCycles ?? 10;
-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const intervalMs = Math.max(1, Number(options.intervalMs ?? 3000));
+  const maxCycles = Math.max(1, Math.floor(Number(options.maxCycles ?? 10)));
+  const initDataHex = options.initDataHex ?? getDefaultInitHex();
+  const signal = options.signal;
+  const withoutResponse = options.withoutResponse ?? true;

39-55: Add abort handling to stop promptly and free resources

Support early cancel and ensure no further scheduling occurs.

   return new Promise<void>((resolve, reject) => {
     let resolved = false;
     let responseReceived = false;
     let cycles = 0;
     let timeoutId: ReturnType<typeof setTimeout> | null = null;
 
     const startTime = Date.now();
 
+    if (signal?.aborted) {
+      resolved = true;
+      reject(new Error('pairing_aborted'));
+      return;
+    }
+
+    const onAbort = () => {
+      if (resolved) return;
+      resolved = true;
+      cleanup();
+      reject(new Error('pairing_aborted'));
+    };
+    signal?.addEventListener('abort', onAbort, { once: true });
+
     const onData = (data: Buffer) => {
       if (resolved || responseReceived) return;
       responseReceived = true;
       const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
       logger?.info('[Pairing] response', { deviceId, cycles, elapsed, len: data.length });
       resolved = true;
       cleanup();
       resolve();
     };

57-64: Cleanup should also unsubscribe and remove abort listener

Leave a clean state and avoid stray notifications.

     const cleanup = () => {
       if (timeoutId) {
         clearTimeout(timeoutId);
         timeoutId = null;
       }
-      // Only remove the specific listener for this probe
-      notifyCharacteristic.removeListener('data', onData);
+      // Only remove the specific listener for this probe
+      notifyCharacteristic.removeListener('data', onData);
+      // Best effort: stop notifications started by the probe
+      try {
+        notifyCharacteristic.unsubscribe(() => {});
+      } catch {
+        /* noop */
+      }
+      // Detach abort handler if attached
+      signal?.removeEventListener('abort', onAbort as any);
     };

66-66: Validate initDataHex; fail fast on bad input

Invalid hex yields empty/short buffers and confusing retries. Validate and reject early.

-    const buffer = Buffer.from(initDataHex, 'hex');
+    const isValidHex =
+      typeof initDataHex === 'string' &&
+      initDataHex.length > 0 &&
+      initDataHex.length % 2 === 0 &&
+      /^[0-9a-fA-F]+$/.test(initDataHex);
+    if (!isValidHex) {
+      logger?.error('[Pairing] invalid initDataHex', { deviceId, length: (initDataHex as any)?.length ?? 0 });
+      resolved = true;
+      cleanup();
+      reject(new Error('invalid initDataHex'));
+      return;
+    }
+    const buffer = Buffer.from(initDataHex, 'hex');

94-121: Wrap unsubscribe in try/catch (bindings can throw synchronously)

Some noble variants throw if already disconnected. Catch and reschedule.

-      notifyCharacteristic.unsubscribe(() => {
-        notifyCharacteristic.subscribe((subscribeError: any) => {
+      try {
+        notifyCharacteristic.unsubscribe(() => {
+          notifyCharacteristic.subscribe((subscribeError: unknown) => {
             if (subscribeError) {
-            logger?.error('[Pairing] subscribe failed', {
+            logger?.error('[Pairing] subscribe failed', {
               deviceId,
               cycle: cycles,
-              error: String(subscribeError ?? 'unknown error'),
+              error: toErrorMessage(subscribeError),
             });
             scheduleNextCycle(); // Reschedule for next attempt
             return;
           }
 
           notifyCharacteristic.once('data', onData);
 
-          writeCharacteristic.write(buffer, true, (writeError: any) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError: unknown) => {
             if (writeError) {
               logger?.error('[Pairing] write failed', {
                 deviceId,
                 cycle: cycles,
-                error: String(writeError ?? 'unknown error'),
+                error: toErrorMessage(writeError),
               });
             } else {
-              logger?.info('[Pairing] write', { deviceId, cycle: cycles });
+              logger?.info('[Pairing] write', { deviceId, cycle: cycles, withoutResponse });
             }
             scheduleNextCycle(); // Reschedule for next attempt
           });
         });
-      });
+        });
+      } catch (e) {
+        logger?.error('[Pairing] unsubscribe threw', { deviceId, cycle: cycles, error: toErrorMessage(e) });
+        scheduleNextCycle();
+      }

10-10: Normalize callback errors; avoid .message on non-Errors

Treat callback errors as unknown and log robustly with a normalizer.

-import type { Logger } from '../types/noble-extended';
+import type { Logger } from '../types/noble-extended';
+import { toErrorMessage } from '../types/noble-extended';
@@
-        notifyCharacteristic.subscribe((subscribeError: any) => {
+        notifyCharacteristic.subscribe((subscribeError?: unknown) => {
@@
-              error: String(subscribeError ?? 'unknown error'),
+              error: toErrorMessage(subscribeError),
             });
@@
-          writeCharacteristic.write(buffer, true, (writeError: any) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError?: unknown) => {
@@
-                error: String(writeError ?? 'unknown error'),
+                error: toErrorMessage(writeError),
               });

Also applies to: 95-101, 108-114


27-34: Add a concise JSDoc describing contract and side effects

Document that the probe temporarily unsubscribes/resubscribes, writes every interval until a response or timeout, logs each step, and resolves on first response or rejects on timeout/abort. This saves future spelunking.

+
+/**
+ * Periodic BLE pairing probe.
+ *
+ * Behavior:
+ * - For each cycle: unsubscribe → subscribe → attach one-time 'data' → write init payload.
+ * - Repeats every `intervalMs` up to `maxCycles`, or until the first notify response arrives.
+ * - Resolves on response. Rejects on timeout or when `options.signal` aborts.
+ *
+ * Side effects:
+ * - Temporarily toggles notifications on `notifyCharacteristic`.
+ * - Leaves notifications off on cleanup.
+ */
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 464fe9d and 80f9162.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/hd-transport-electron/src/utils/blePairing.ts (1)

68-90: Good sequencing: single-flight cycles and early-resolve guard

Using setTimeout to schedule the next cycle only after the write callback avoids overlapping probe cycles. The resolved/responseReceived checks also short‑circuit late timers. Nice.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (18)
packages/shared/package.json (1)

3-3: Version bump looks good; add publish hygiene and tree‑shaking metadata.

Add a minimal publish whitelist and mark the package as side‑effect free to keep installs lean and enable tree‑shaking.

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-electron/package.json (3)

23-26: Declare Bluetooth HCI socket as an optional peer to guide Linux users.

Most Linux setups need this, but it should remain optional for macOS/Windows.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

16-21: Add electron-rebuild to ensure native modules match Electron’s ABI.

Without a rebuild step, native deps (HCI socket, noble) can fail at runtime with Electron >= 25.

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
     "lint": "eslint .",
-    "lint:fix": "eslint . --fix"
+    "lint:fix": "eslint . --fix",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket"
   },
   "devDependencies": {
     "@types/web-bluetooth": "^0.0.17",
     "electron": "^25.0.0",
-    "typescript": "^5.3.3"
+    "typescript": "^5.3.3",
+    "electron-rebuild": "^3.6.0"
   }

Run this script to verify there are no leftover abandonware imports and that a rebuild hook exists:

#!/usr/bin/env bash
set -euo pipefail
echo "1) Ensure only @stoprocent noble is referenced"
rg -nP "@abandonware/noble" -C2 || echo "OK: no @abandonware/noble references"

echo "2) Check for electron-rebuild or prebuild hooks"
rg -nP 'electron-rebuild|prebuild' -C2 || echo "WARN: no rebuild/prebuild hooks found"

echo "3) Spot any direct imports of @stoprocent/bluetooth-hci-socket"
rg -nP "from ['\"]@stoprocent/bluetooth-hci-socket['\"]|require\\(['\"]@stoprocent/bluetooth-hci-socket['\"]\\)" -C2 || echo "OK: no direct imports (kept optional)"

Also applies to: 34-38


27-33: Loosen noble to pick up patch fixes automatically.

Use a caret for @stoprocent/noble so you receive patch updates without manual bumps.

   "dependencies": {
     "@onekeyfe/hd-core": "1.1.10",
     "@onekeyfe/hd-shared": "1.1.10",
     "@onekeyfe/hd-transport": "1.1.10",
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
     "p-retry": "^4.6.2"
   },
packages/hd-transport-lowlevel/package.json (1)

22-23: Consider workspace protocol for internal deps to ease future bumps.

Switching to workspace:* reduces churn when you cut new releases.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }
packages/hd-transport-react-native/package.json (1)

22-23: Use workspace protocol for internal deps for smoother releases.

This keeps RN package aligned automatically with the monorepo versions.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
     "@onekeyfe/react-native-ble-utils": "^0.1.4",
     "react-native-ble-plx": "3.5.0"
   }
packages/connect-examples/electron-example/package.json (4)

32-32: Drop node-fetch; use global fetch (Node 18+/Electron)

Electron 28 ships Node 18+. Prefer the built-in Fetch API to reduce deps and native rebuilds.

-    "node-fetch": "^2.6.7"

Follow-up: replace imports with global fetch (or undici if you need specific features).


18-20: Add CI-safe build scripts to avoid accidental publishing

Provide make:*:ci variants that never publish artifacts in CI.

   "scripts": {
     "build:main": "webpack --config webpack.config.ts",
     "rebuild:deps": "electron-builder install-app-deps",
-    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
-    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
+    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",

25-27: Missing HCI socket dependency will break Linux BLE at runtime

@stoprocent/bluetooth-hci-socket is optional in noble and often not installed; add it explicitly to avoid module-not-found in Electron main on Linux.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "2.2.3",

7-7: Fix the package description

This is an Electron hardware example, not “End-to-end encrypted workspaces”.

-  "description": "End-to-end encrypted workspaces for teams",
+  "description": "OneKey hardware Electron example app",
packages/hd-transport-http/package.json (1)

27-31: Upgrade axios to patched 1.x; 0.27.x carries known vulns

Bump axios to a secure 1.x (e.g., ^1.11.0). Run tests to catch breaking changes.

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10",
     "@onekeyfe/hd-transport": "1.1.10",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }

Verification script:

#!/bin/bash
set -euo pipefail
cd packages/hd-transport-http
npm install --package-lock-only --legacy-peer-deps
npm ls axios --depth=0 || true
npm audit --omit=dev || true
packages/hd-transport-emulator/package.json (1)

27-31: Patch axios here too to keep security posture consistent

Mirror the axios upgrade to ^1.11.0 in the emulator transport.

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10",
     "@onekeyfe/hd-transport": "1.1.10",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }

Quick checks:

#!/bin/bash
set -euo pipefail
cd packages/hd-transport-emulator
npm install --package-lock-only --legacy-peer-deps
npm ls axios --depth=0 || true
npm audit --omit=dev || true
packages/hd-ble-sdk/package.json (1)

23-25: Optional: use workspace protocol for internal deps

This eases coordinated bumps across the monorepo.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-react-native": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }
packages/core/package.json (1)

28-29: Optional: switch internal deps to workspace protocol

Using workspace:^ keeps internal packages in sync and reduces manual bumps.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^",
     "axios": "^0.27.2",
packages/hd-web-sdk/package.json (1)

24-27: Optional: adopt workspace protocol for internal deps

Keeps the monorepo in lockstep without manual version churn.

   "dependencies": {
     "@onekeyfe/cross-inpage-provider-core": "^0.0.17",
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-http": "1.1.10",
-    "@onekeyfe/hd-transport-web-device": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport-http": "workspace:^",
+    "@onekeyfe/hd-transport-web-device": "workspace:^"
   }
packages/hd-transport-web-device/package.json (1)

23-24: Workspace protocol optional; also verify 1.1.10 publishes (incl. devDep)

  • Consider workspace:^ for internal deps to avoid manual bumps.
  • Confirm @onekeyfe/[email protected] is published (dev installs/builds rely on it).

Refactor:

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10"
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^"
   },
   "devDependencies": {
-    "@onekeyfe/hd-transport-electron": "1.1.10",
+    "@onekeyfe/hd-transport-electron": "workspace:^",

Publish check:

#!/bin/bash
for p in \
  "@onekeyfe/hd-shared" \
  "@onekeyfe/hd-transport" \
  "@onekeyfe/hd-transport-electron"
do
  echo -n "$p@1.1.10 => "
  npm view "$p@1.1.10" version --silent || echo "NOT PUBLISHED"
done

Also applies to: 27-27

packages/hd-common-connect-sdk/package.json (1)

23-28: Confirm all internal deps are published at 1.1.10; consider workspaces

  • Verify publish status to prevent install errors.
  • Optional: switch to workspace:^ to minimize manual bumps.

Publish check:

#!/bin/bash
pkgs=(
  "@onekeyfe/hd-core"
  "@onekeyfe/hd-shared"
  "@onekeyfe/hd-transport-emulator"
  "@onekeyfe/hd-transport-http"
  "@onekeyfe/hd-transport-lowlevel"
  "@onekeyfe/hd-transport-web-device"
)
for p in "${pkgs[@]}"; do
  echo -n "$p@1.1.10 => "
  npm view "$p@1.1.10" version --silent || echo "NOT PUBLISHED"
done

Workspace option:

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-emulator": "1.1.10",
-    "@onekeyfe/hd-transport-http": "1.1.10",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10",
-    "@onekeyfe/hd-transport-web-device": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport-emulator": "workspace:^",
+    "@onekeyfe/hd-transport-http": "workspace:^",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:^",
+    "@onekeyfe/hd-transport-web-device": "workspace:^"
   }
packages/connect-examples/expo-example/package.json (1)

10-10: Use a single cross-env in build script

One cross-env can set all env vars; it’s cleaner.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 80f9162 and 5b8b884.

📒 Files selected for processing (15)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (6)
packages/hd-transport-electron/package.json (1)

3-3: Version alignment to 1.1.10 looks consistent across the stack.

No concerns on the version bump here.

packages/hd-transport-lowlevel/package.json (1)

3-3: Version bump to 1.1.10 looks consistent.

All good on the version alignment.

packages/hd-transport-react-native/package.json (1)

3-3: Stable 1.1.10 version looks good.

No issues with the version bump here.

packages/connect-examples/electron-example/package.json (1)

5-5: Version bump looks good

Stable 1.1.10 aligns with the monorepo updates.

packages/connect-examples/expo-playground/package.json (1)

3-3: Version bump to 1.1.10 looks good

Matches the rest of the workspace.

packages/hd-ble-sdk/package.json (1)

3-3: Version alignment to 1.1.10 looks good

No issues spotted.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (18)
packages/shared/package.json (1)

3-3: LGTM on the version bump. Consider adding publish hygiene.

Looks good. To keep installs lean and enable tree‑shaking, add sideEffects: false and a files whitelist.

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10-alpha.6",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport/package.json (1)

3-4: Nice: real description and aligned version. Add sideEffects/files for slimmer publishes.

Good cleanup. Repeat suggestion: mark as side‑effect free and whitelist published files.

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10-alpha.6",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-react-native/package.json (1)

22-23: Internal deps: prefer workspace protocol to reduce manual bumps.

This keeps packages in lockstep in the monorepo.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.6",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^",
     "@onekeyfe/react-native-ble-utils": "^0.1.4",
     "react-native-ble-plx": "3.5.0"
   }

Optionally add publish hygiene too:

   "types": "dist/index.d.ts",
+  "sideEffects": false,
+  "files": ["dist/**/*","README.md","LICENSE","package.json"],
packages/core/package.json (1)

28-29: Ensure deps are published or use workspace protocol.

Alpha.6 pins are fine if they’re published. Otherwise, workspace protocol avoids churn inside the mono.

Verification:

#!/bin/bash
set -euo pipefail
for p in "@onekeyfe/hd-shared" "@onekeyfe/hd-transport"; do
  echo -n "$p@1.1.10-alpha.6 => "
  npm view "$p@1.1.10-alpha.6" version --silent || echo "NOT PUBLISHED"
done

Optional workspace switch:

-    "@onekeyfe/hd-shared": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.6",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^",
packages/hd-ble-sdk/package.json (1)

23-25: Optional: switch internal deps to workspace protocol for easier bumps.

Keeps packages in lockstep without manual edits. Mirrors earlier feedback.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.6",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.6"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }
packages/hd-transport-lowlevel/package.json (1)

22-23: Optional: use workspace: for internal deps.*

Reduces churn in future coordinated releases. Reiterating earlier note.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.6"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }
packages/hd-transport-http/package.json (1)

26-31: Upgrade axios to a secure 1.x line.

axios ^0.27.2 carries known advisories; prior reviews flagged this. Please bump to a patched 1.x and run tests/audit.

Apply:

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.6",
     "@onekeyfe/hd-transport": "1.1.10-alpha.6",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }

Then verify:

#!/bin/bash
set -euo pipefail
cd packages/hd-transport-http
npm install --package-lock-only --legacy-peer-deps
npm ls axios --depth=0 || true
npm audit --omit=dev || true
npm run -s build || true
packages/hd-transport-emulator/package.json (1)

26-31: Mirror the axios upgrade here to keep security posture consistent.

Still on ^0.27.2. Please bump to 1.x and re-run tests/audit. This echoes prior guidance.

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.6",
     "@onekeyfe/hd-transport": "1.1.10-alpha.6",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }

Quick check:

#!/bin/bash
set -euo pipefail
cd packages/hd-transport-emulator
npm install --package-lock-only --legacy-peer-deps
npm ls axios --depth=0 || true
npm audit --omit=dev || true
npm run -s build || true
packages/hd-transport-electron/package.json (3)

22-26: Declare HCI socket as an optional peer for Linux users.

Guides integrators without forcing installs on macOS/Windows. Reiterating previous suggestion.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

16-21: Add postinstall rebuild for native modules under Electron.

Noble/HCI often needs ABI rebuilds for Electron. Add an electron-rebuild hook to avoid runtime load errors. Echoing prior advice.

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket || true",
     "lint": "eslint .",
     "lint:fix": "eslint . --fix"
   },

Verification steps (run in electron package and in the example app):

#!/bin/bash
set -euo pipefail
# 1) Ensure electron-rebuild is available (either devDep here or in root)
npm exec -c "electron-rebuild --version" || echo "electron-rebuild not found"

# 2) Rebuild natives for current Electron
npm run -s postinstall || true

# 3) Sanity: check for native bindings presence
rg -nP "(?i)node-addon-api|napi|.node\\b" -C2 || true

28-33: Use a caret for Noble to get patch fixes automatically.

Avoids pinning to a single patch unless you need strict reproducibility. Mirrors an earlier note.

   "dependencies": {
     "@onekeyfe/hd-core": "1.1.10-alpha.6",
     "@onekeyfe/hd-shared": "1.1.10-alpha.6",
     "@onekeyfe/hd-transport": "1.1.10-alpha.6",
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
     "p-retry": "^4.6.2"
   }
packages/hd-web-sdk/package.json (1)

24-27: Align internal deps via workspace protocol to cut version-churn.

Use workspace:* so these stay in lockstep across the monorepo.

-    "@onekeyfe/hd-core": "1.1.10-alpha.6",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.6"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
packages/connect-examples/expo-example/package.json (1)

10-11: Use a single cross-env invocation.

Simpler and clearer. Keeps the same behavior.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
packages/hd-common-connect-sdk/package.json (1)

23-28: Adopt workspace protocol for all internal deps.

Reduces manual version bumps and mismatch risk.

-    "@onekeyfe/hd-core": "1.1.10-alpha.6",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport-emulator": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10-alpha.6",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.6"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-emulator": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
packages/connect-examples/electron-example/package.json (4)

32-32: Drop node-fetch; use global fetch (Electron/Node ≥18).

Reduces deps and native rebuilds. Only keep if you rely on v2-specific APIs.

-    "node-fetch": "^2.6.7"

7-7: Fix the package description.

This is an Electron hardware example, not “End-to-end encrypted workspaces.”

-  "description": "End-to-end encrypted workspaces for teams",
+  "description": "OneKey hardware Electron example app",

18-21: Add CI-safe build scripts that never publish.

Keep make:* for local publish. Add make:*:ci to build-only.

   "scripts": {
@@
     "rebuild:deps": "electron-builder install-app-deps",
     "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
     "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",

25-27: Add @stoprocent/bluetooth-hci-socket explicitly.

Noble needs the HCI socket on Linux. Adding it avoids “module not found” at runtime.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.6",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5b8b884 and 69424dc.

📒 Files selected for processing (15)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build (22)
🔇 Additional comments (10)
packages/hd-transport-react-native/package.json (1)

3-3: Version alignment looks good.

No issues spotted with the bump.

packages/hd-ble-sdk/package.json (1)

3-3: Version alignment LGTM.

Bump to 1.1.10-alpha.6 matches the monorepo. No issues spotted.

packages/hd-transport-lowlevel/package.json (1)

3-3: Version bump looks good.

Matches the coordinated alpha.6 rollout.

packages/hd-transport-http/package.json (1)

3-3: Version alignment LGTM.

Package version and internal deps align with alpha.6.

packages/hd-transport-emulator/package.json (1)

3-3: Version update LGTM.

Consistent with the alpha.6 sweep.

packages/hd-transport-electron/package.json (1)

3-3: Version alignment LGTM.

Matches the monorepo alpha.6 series.

packages/hd-web-sdk/package.json (1)

3-3: Version bump looks consistent.

Nice alignment with sibling packages.

packages/hd-transport-web-device/package.json (1)

3-3: Version alignment looks good.

No issues spotted with the bump.

packages/hd-common-connect-sdk/package.json (1)

3-3: Version bump is consistent.

All good here.

packages/connect-examples/electron-example/package.json (1)

25-26: Native modules are correctly externalized and unpacked

Webpack externals include both @stoprocent/noble and @stoprocent/bluetooth-hci-socket
Electron-builder’s asarUnpack covers the same modules

No further changes needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (18)
package.json (2)

34-34: Make Lerna publish non-interactive in CI

Avoid CI hangs by adding --yes to lerna publish.

-    "publish-packages": "yarn check-versions && lerna publish from-package",
+    "publish-packages": "yarn check-versions && lerna publish from-package --yes",

40-41: Add CI-safe desktop build scripts (no publish)

Provide explicit CI variants to prevent accidental publishing from CI runners.

     "build:example:desktop:mac": "cd ./packages/connect-examples/electron-example && yarn make:mac",
     "build:example:desktop:win": "cd ./packages/connect-examples/electron-example && yarn make:win",
+    "build:example:desktop:mac:ci": "cd ./packages/connect-examples/electron-example && yarn make:mac:ci",
+    "build:example:desktop:win:ci": "cd ./packages/connect-examples/electron-example && yarn make:win:ci",

Follow-up: update the workflow to call the :ci scripts.

packages/hd-transport-electron/src/utils/blePairing.ts (5)

29-35: Drop unnecessary async

The function manually constructs a Promise. Remove async for clarity.

-export async function runPairingProbe(
+export function runPairingProbe(

86-88: Validate initDataHex; fail fast on bad hex

Invalid hex yields empty/short buffers and silent retries. Validate and reject early.

-    const buffer = Buffer.from(initDataHex, 'hex');
+    const isValidHex =
+      typeof initDataHex === 'string' &&
+      initDataHex.length > 0 &&
+      initDataHex.length % 2 === 0 &&
+      /^[0-9a-fA-F]+$/.test(initDataHex);
+    if (!isValidHex) {
+      logger?.error('[Pairing] invalid initDataHex', { deviceId, len: initDataHex?.length ?? 0 });
+      cleanup();
+      reject(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, 'invalid initDataHex'));
+      return;
+    }
+    const buffer = Buffer.from(initDataHex, 'hex');

73-73: Add once:true to abort listener

Prevents dangling listeners if cleanup is missed.

-    abortSignal?.addEventListener('abort', onAbort);
+    abortSignal?.addEventListener('abort', onAbort, { once: true });

75-84: Consider unsubscribing in cleanup to leave a clean state

If callers always re-subscribe, unsubscribe here to prevent transient double subscriptions.

     const cleanup = () => {
       if (timeoutId) {
         clearTimeout(timeoutId);
         timeoutId = null;
       }
       // Only remove the specific listener for this probe
       notifyCharacteristic.removeListener('data', onData);
       // Remove abort listener
       abortSignal?.removeEventListener('abort', onAbort);
+      try {
+        // Best-effort unsubscribe; ignore errors
+        (notifyCharacteristic as any)?.unsubscribe?.(() => {});
+      } catch {}
     };

114-141: Guard unsubscribe against sync throws; normalize subscribe errors

Some bindings throw synchronously on unsubscribe. Catch and reschedule. Also treat callback errors as unknown.

-      notifyCharacteristic.unsubscribe(() => {
-        notifyCharacteristic.subscribe((subscribeError: any) => {
+      try {
+        notifyCharacteristic.unsubscribe(() => {
+          notifyCharacteristic.subscribe((subscribeError: unknown) => {
             if (subscribeError) {
               logger?.error('[Pairing] subscribe failed', {
                 deviceId,
                 cycle: cycles,
-              error: String(subscribeError ?? 'unknown error'),
+              error: String((subscribeError as any)?.message ?? subscribeError ?? 'unknown error'),
               });
               scheduleNextCycle(); // Reschedule for next attempt
               return;
             }
 
             notifyCharacteristic.once('data', onData);
 
-          writeCharacteristic.write(buffer, true, (writeError: any) => {
+          writeCharacteristic.write(buffer, true, (writeError: unknown) => {
             if (writeError) {
               logger?.error('[Pairing] write failed', {
                 deviceId,
                 cycle: cycles,
-              error: String(writeError ?? 'unknown error'),
+              error: String((writeError as any)?.message ?? writeError ?? 'unknown error'),
               });
             } else {
               logger?.info('[Pairing] write', { deviceId, cycle: cycles });
             }
             scheduleNextCycle(); // Reschedule for next attempt
           });
         });
-      });
+      } catch (e) {
+        logger?.error('[Pairing] unsubscribe threw', { deviceId, cycle: cycles, error: String(e) });
+        scheduleNextCycle();
+      }
packages/hd-transport-electron/src/noble-ble-handler.ts (11)

983-996: Connect callback: normalize error before constructing TypedError

Avoid .message on unknown error shapes.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
@@
-      if (error) {
+      if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }

27-27: Typo in comment

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

118-123: Reduce notification log noise to debug

Per-notification info logs are noisy and can hurt perf. Use debug level via safeLog.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

724-729: Force reconnect: normalize error message

Avoid accessing .message on non-Error.

-        logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        logger?.error('[NobleBLE] Force reconnect failed:', error);
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));

775-778: Fresh peripheral connect: normalize error message

Same normalization here.

-        if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+        if (error) {
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));

1381-1395: Gate console logs behind a debug flag or use logger

Reduce noise and keep logs consistent.

-  // Use console.log for initial logging as electron-log might not be available yet.
-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  // Minimal bootstrap log before electron-log is available.
+  if (process.env.ONEKEY_BLE_DEBUG) {
+    // eslint-disable-next-line no-console
+    console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  }
@@
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      // eslint-disable-next-line no-console
+      console.log('[NobleBLE] NOBLE bootstrap');
+    }
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron-log loaded.');
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron.ipcMain loaded.');
@@
-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    }

1448-1449: IPC write log: report bytes, not hex chars

hexData.length counts nibbles. Divide by 2.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

1184-1189: Guard against subscribe/unsubscribe races

If state is 'unsubscribing', wait before subscribing to avoid races.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    // Wait until unsubscribe completes
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1124-1133: Do not log full chunk payloads

Full hex dumps can leak data and bloat logs. Log a preview and sizes only.

-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
+        chunkPreview: `${preview}...`,
         withResponse: false,
       });

1311-1324: Probe write: comment contradicts code; also normalize error type

Comment says “doesn't expect a response” but code uses with-response (false). Use withoutResponse=true or fix the comment. Normalize error logging.

-      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
-      // This is a dummy write operation that doesn't expect a response.
+      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
+      // This is a dummy write operation; no response is expected.
       logger?.info(`[NobleBLE] Performing probe write to trigger pairing on ${process.platform}`);
       const probeData = Buffer.from('01', 'hex'); // A single byte is sufficient
       await new Promise<void>((resolve, reject) => {
-        characteristics.write.write(probeData, false, error => {
-          if (error) {
+        characteristics.write.write(probeData, true, (error: unknown) => {
+          if (error) {
             // We can often ignore errors here, as the goal is just to trigger the dialog.
             // However, log it for debugging purposes.
-            logger?.error('[NobleBLE] Probe write failed (this may be expected):', error.message);
+            logger?.debug('[NobleBLE] Probe write failed (this may be expected):', toErrorMessage(error));
           }
           resolve(); // Resolve regardless of error
         });
       });

100-100: Action Required: Make CONNECTION_TIMEOUT configurable and update default

Verified that CONNECTION_TIMEOUT is hardcoded to 3 000 ms at line 100 in packages/hd-transport-electron/src/noble-ble-handler.ts. There’s no existing environment override in this file. A 3 s timeout will likely fire before a full 30 s pairing window completes.

• Location: packages/hd-transport-electron/src/noble-ble-handler.ts:100
• No process.env usage found for BLE timeouts

Suggested refactor:

-const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+const CONNECTION_TIMEOUT = Number(
+  process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 30000
+); // default to 30 seconds for device connection

Please confirm with the firmware/device team that 30 000 ms aligns with the auto-pairing timeout. If their spec differs, choose a safe default that won’t preempt the probe.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1a9e12f and 5c22558.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • package.json (4 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (25 hunks)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
  • packages/shared/src/constants.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (2)
packages/shared/src/constants.ts (1)

42-42: New IPC key for cancel pairing: LGTM

Enum entry NOBLE_BLE_CANCEL_PAIRING looks good and matches the handler added in noble-ble-handler.ts. No issues.

packages/hd-transport-electron/src/noble-ble-handler.ts (1)

21-21: Ensure TypeScript types for ‘@stoprocent/noble’

I didn’t find any local shim or built-in declarations for @stoprocent/noble. Please verify that the package actually ships TypeScript types—or else import types from the official noble package or add your own shim.

• Check that @stoprocent/noble’s package.json has a "types" field or an index.d.ts in its npm bundle.
• If it doesn’t, switch the import to pull types from noble at compile-time and keep require('@stoprocent/noble') at runtime.
• Alternatively, add a local declaration file (e.g. types/stoprocent-noble.d.ts) with:

declare module '@stoprocent/noble';

Suggested diff:

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/connect-examples/electron-example/package.json (1)

32-32: Remove node-fetch v2 (security + redundancy)

Node 18+/Electron has global fetch. node-fetch v2 carries known CVEs and native rebuilds.

-    "node-fetch": "^2.6.7"
+    // Use global fetch (Node 18+/Electron); no dependency needed

Follow-up: replace any node-fetch imports with global fetch/Request/Response.

packages/hd-transport-electron/src/noble-ble-handler.ts (1)

440-451: Memory leak: discover listener not removed on timeout

on timeout path you don't remove the 'discover' listener. Clean up consistently.

-  return new Promise((resolve, reject) => {
-    const timeout = setTimeout(() => {
-      if (noble) {
-        noble.stopScanning();
-      }
-      logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
-      resolve(null);
-    }, FAST_SCAN_TIMEOUT);
+  return new Promise((resolve, reject) => {
+    const cleanup = () => {
+      if (!noble) return;
+      noble.stopScanning();
+      noble.removeListener('discover', onDiscover);
+    };
+    const timeout = setTimeout(() => {
+      cleanup();
+      logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
+      resolve(null);
+    }, FAST_SCAN_TIMEOUT);
@@
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    if (noble) noble.on('discover', onDiscover);

Also applies to: 472-476

♻️ Duplicate comments (35)
packages/hd-transport/package.json (1)

3-4: Nice description update; add sideEffects and files for lean publishes.

Mark the package side‑effect free and whitelist publish files to reduce install size and improve tree‑shaking.

Apply:

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10-alpha.7",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
   "author": "OneKey",
package.json (1)

33-35: Make publish non‑interactive and add CI‑safe desktop build scripts.

Avoid stuck CI and accidental publishes from CI runs.

Apply:

   "scripts": {
@@
-    "check-versions": "node scripts/check-versions.js",
-    "publish-packages": "yarn check-versions && lerna publish from-package",
+    "check-versions": "node scripts/check-versions.js",
+    "publish-packages": "yarn check-versions && lerna publish from-package --yes",
@@
-    "build:example:desktop:mac": "cd ./packages/connect-examples/electron-example && yarn make:mac",
-    "build:example:desktop:win": "cd ./packages/connect-examples/electron-example && yarn make:win",
+    "build:example:desktop:mac": "cd ./packages/connect-examples/electron-example && yarn make:mac",
+    "build:example:desktop:win": "cd ./packages/connect-examples/electron-example && yarn make:win",
+    "build:example:desktop:mac:ci": "cd ./packages/connect-examples/electron-example && cross-env ELECTRON_BUILDER_SKIP_PUBLISH=1 yarn make:mac",
+    "build:example:desktop:win:ci": "cd ./packages/connect-examples/electron-example && cross-env ELECTRON_BUILDER_SKIP_PUBLISH=1 yarn make:win",
@@
-    "dev:transport-electron": "cd ./packages/hd-transport-electron && yarn dev",
+    "dev:transport-electron": "cd ./packages/hd-transport-electron && yarn dev",

Run:

#!/bin/bash
# Ensure pre-commit runs version checks.
test -f .husky/pre-commit && cat .husky/pre-commit

# Dry-run the version checker.
node scripts/check-versions.js || true

Also applies to: 40-41, 54-54

packages/shared/package.json (1)

3-3: Version bump OK; add sideEffects and a files whitelist.

Keep the shared package light and tree‑shakable.

Apply:

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10-alpha.7",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/connect-examples/expo-playground/package.json (1)

3-3: Switch to workspace protocol for local packages

None of the 1.1.10-alpha.7 tags are published on npm, so the Expo Playground example will fail to install. In packages/connect-examples/expo-playground/package.json, update the three local deps under dependencies:

--- a/packages/connect-examples/expo-playground/package.json
+++ b/packages/connect-examples/expo-playground/package.json
@@ -20,7 +20,7 @@
   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.7",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.7",
-    "@onekeyfe/hd-web-sdk": "1.1.10-alpha.7",
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-web-sdk": "workspace:^",
     "expo": "^48.0.0",
     "react": "^18.0.0",
     "react-native": "0.71.8"

This switch ensures the example installs cleanly against your local packages.

packages/hd-transport-lowlevel/package.json (1)

22-23: Consider workspace protocol for internal deps

Switching internal packages to "workspace:*" simplifies future bumps and avoids manual churn. This mirrors prior feedback.

Apply:

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.7"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }
packages/hd-transport-react-native/package.json (1)

22-23: Use workspace protocol for internal deps

Repeat of earlier suggestion: prefer "workspace:" for @onekeyfe/ internals to reduce manual version churn.

Apply:

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.7",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
     "@onekeyfe/react-native-ble-utils": "^0.1.4",
     "react-native-ble-plx": "3.5.0"
   }
packages/hd-transport-electron/package.json (3)

23-26: Add optional peer for @stoprocent/bluetooth-hci-socket (Linux)

Guide Linux integrators without forcing installs on macOS/Windows. Mirrors prior feedback.

Apply:

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

16-21: Ensure native modules rebuild for Electron >=25

Noble’s stack may include native addons. Add a postinstall rebuild to avoid ABI mismatches.

Apply:

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
     "lint": "eslint .",
-    "lint:fix": "eslint . --fix"
+    "lint:fix": "eslint . --fix",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket"
   },

Run to check for existing rebuild hooks/imports:

#!/bin/bash
set -euo pipefail
rg -nP 'electron-rebuild|prebuild' -C2 || true
rg -n '@stoprocent/bluetooth-hci-socket' -C2 || true

31-31: Use a caret for noble to pick up patch fixes

Let patch updates flow unless you have a known regression. This was suggested before.

Apply:

-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
packages/core/package.json (2)

28-29: Optional: switch internal deps to workspace protocol.

Cuts manual version churn across releases.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.7",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^",

3-4: Optional publish hygiene: add sideEffects/files.

Helps tree‑shaking and trims the tarball.

   "description": "Core processes and APIs for communicating with OneKey hardware devices.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-ble-sdk/package.json (1)

23-25: Consider workspace protocol for internal deps.

Keeps packages in sync during dev.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.7",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.7"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }
packages/hd-transport-web-device/package.json (1)

23-24: Optional: workspace protocol for internal deps.

Reduces version churn.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.7"
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^"
   },
packages/connect-examples/expo-example/package.json (1)

10-10: Use a single cross-env.

No need to chain cross-env twice. This is cleaner.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
packages/hd-common-connect-sdk/package.json (2)

3-3: Add CHANGELOG entry and tag for 1.1.10-alpha.7

Keep release bookkeeping in sync with the version bump.

Run to verify:

#!/bin/bash
# From repo root
rg -n "## \\[1\\.1\\.10-alpha\\.7\\]" --glob '!**/node_modules/**' || echo "Missing CHANGELOG entry"
git tag -l | rg "^1\\.1\\.10-alpha\\.7$" || echo "Missing git tag 1.1.10-alpha.7"

23-28: Prefer workspace protocol for internal deps

Reduces churn across the monorepo and keeps versions aligned.

-    "@onekeyfe/hd-core": "1.1.10-alpha.7",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport-emulator": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10-alpha.7",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.7"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-emulator": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
packages/hd-transport-electron/src/utils/blePairing.ts (4)

21-27: Lazy-load DEFAULT_INIT_DATA (avoid module-load side effects)

Defers protobuf parsing/build until needed; safer and faster on load.

-// Get protobuf definitions
-const messages = DataManager.getProtobufMessages();
-const messagesRoot = parseConfigure(messages as any);
-
-// Dynamically build the Initialize message
-const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
-const DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+let DEFAULT_INIT_DATA: string | null = null;
+function getDefaultInitHex(): string {
+  if (DEFAULT_INIT_DATA) return DEFAULT_INIT_DATA;
+  const messages = DataManager.getProtobufMessages();
+  const messagesRoot = parseConfigure(messages as any);
+  const initializeMessageBuffer = buildOne(messagesRoot, 'Initialize', {});
+  DEFAULT_INIT_DATA = initializeMessageBuffer.toString('hex');
+  return DEFAULT_INIT_DATA;
+}
@@
-  const initDataHex = options.initDataHex ?? DEFAULT_INIT_DATA;
+  const initDataHex = options.initDataHex ?? getDefaultInitHex();

Also applies to: 38-38


86-86: Validate initDataHex before Buffer.from

Fail fast on bad hex; avoids silent retries.

-    const buffer = Buffer.from(initDataHex, 'hex');
+    const isValidHex =
+      typeof initDataHex === 'string' &&
+      initDataHex.length > 0 &&
+      (initDataHex.length % 2 === 0) &&
+      /^[0-9a-fA-F]+$/.test(initDataHex);
+    if (!isValidHex) {
+      logger?.error('[Pairing] invalid initDataHex', { deviceId, len: initDataHex?.length ?? 0 });
+      resolved = true;
+      cleanup();
+      reject(new Error('invalid initDataHex'));
+      return;
+    }
+    const buffer = Buffer.from(initDataHex, 'hex');

75-84: Unsubscribe in cleanup to leave a clean state

Prevents lingering subscriptions if caller exits early.

   const cleanup = () => {
     if (timeoutId) {
       clearTimeout(timeoutId);
       timeoutId = null;
     }
     // Only remove the specific listener for this probe
     notifyCharacteristic.removeListener('data', onData);
     // Remove abort listener
     abortSignal?.removeEventListener('abort', onAbort);
+    try {
+      notifyCharacteristic.unsubscribe(() => {});
+    } catch { /* noop */ }
   };

14-19: Make write response mode configurable

Expose with/without response as an option; default stays as-is.

 export interface Step3Options {
   intervalMs?: number; // default 3000
   maxCycles?: number; // default 10
   initDataHex?: string; // hex string to write; default OneKey init
-  abortSignal?: AbortSignal; // signal to cancel the pairing probe
+  abortSignal?: AbortSignal; // signal to cancel the pairing probe
+  withoutResponse?: boolean; // default true
 }
@@
-  const { abortSignal } = options;
+  const { abortSignal } = options;
+  const withoutResponse = options.withoutResponse ?? true;
@@
-          writeCharacteristic.write(buffer, true, (writeError: any) => {
+          writeCharacteristic.write(buffer, withoutResponse, (writeError: any) => {

Also applies to: 128-139

packages/connect-examples/electron-example/package.json (3)

7-7: Fix package description

Make it match the actual app.

-  "description": "End-to-end encrypted workspaces for teams",
+  "description": "OneKey hardware Electron example app",

25-27: Ensure HCI socket is installed (Linux/Electron)

Add the native HCI socket explicitly to avoid runtime module-not-found.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.7",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",

19-20: Add non-publishing CI build scripts

Prevents accidental publishes from CI.

   "scripts": {
     "rebuild:deps": "electron-builder install-app-deps",
     "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
     "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",
packages/hd-transport-electron/src/noble-ble-handler.ts (12)

95-101: Connection timeout is likely too short; make it configurable

3s may fire before Windows pairing completes. Default to 30s or env override.

-const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 30000);

704-731: Use toErrorMessage in reconnect flows

Avoid unsafe .message and keep error text consistent.

-        logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        logger?.error('[NobleBLE] Force reconnect failed:', error);
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));

Similarly for "Fresh peripheral connection failed".

Also applies to: 741-783


21-21: Type imports: avoid relying on @stoprocent/noble for d.ts

Some forks don’t ship types. Import types from 'noble' or add a shim.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

If you prefer staying on @stoprocent/noble, add a local .d.ts that re-exports noble types.


118-123: Reduce notification log noise

Use debug to avoid flooding logs and hurting perf.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

621-627: Normalize callback errors; avoid .message on non-Errors

Noble may pass strings. Use a safe normalizer everywhere.

+import { safeLog, toErrorMessage } from './types/noble-extended';
@@
-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
-          logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          logger?.error('[NobleBLE] Service discovery failed:', error);
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
           return;
         }
@@
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
               return;
             }
@@
-    peripheral.connect((error: Error | undefined) => {
+    peripheral.connect((error: unknown) => {
@@
-      if (error) {
+      if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }

Apply the same to force reconnect, fresh peripheral connect, chunk write reject, and unsubscribe paths shown in the ranges above.

Also applies to: 642-647, 724-729, 775-778, 990-996, 1129-1135, 1359-1365


1075-1095: Write path: safer defaults and less-sensitive logs

  • Consider with-response for control packets.
  • Don’t log full chunk hex; log a short preview.
-    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length });
+    const withoutResponse = false; // safer for small control writes
+    logger?.info('[NobleBLE] Write single', { deviceId, size: buffer.length, withoutResponse });
@@
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
+        chunkPreview: `${preview}...`,
         withResponse: false,
       });
@@
-      writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+      writeCharacteristic.write(chunk, true, (error: unknown) => {
         if (error) {
           logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, toErrorMessage(error)));
           return;
         }

Also applies to: 1124-1131


1182-1189: Guard subscribe against in-flight unsubscribe

Wait until operation state is idle to prevent races.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1231-1263: Normalize (un)subscribe callback errors and fix probe write semantics

Handle unknown errors, and align comment with write flag.

-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });
@@
-      // On non-Windows platforms, a probe write is needed to trigger the pairing dialog.
-      // This is a dummy write operation that doesn't expect a response.
+      // On non-Windows platforms, a probe write can trigger the pairing dialog.
+      // This is a dummy write; we don't require a response.
       logger?.info(`[NobleBLE] Performing probe write to trigger pairing on ${process.platform}`);
       const probeData = Buffer.from('01', 'hex'); // A single byte is sufficient
       await new Promise<void>((resolve, reject) => {
-        characteristics.write.write(probeData, false, error => {
+        characteristics.write.write(probeData, true, (error: unknown) => {
           if (error) {
-            // We can often ignore errors here, as the goal is just to trigger the dialog.
-            // However, log it for debugging purposes.
-            logger?.error('[NobleBLE] Probe write failed (this may be expected):', error.message);
+            // Often ignorable; log for diagnostics.
+            logger?.debug('[NobleBLE] Probe write failed (expected on some stacks):', toErrorMessage(error));
           }
           resolve(); // Resolve regardless of error
         });
       });

Also applies to: 1294-1324


1381-1395: Replace stray console.log with gated debug

Keep production logs consistent and quiet by default.

-  // Use console.log for initial logging as electron-log might not be available yet.
-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  // Minimal bootstrap log before electron-log is available.
+  if (process.env.ONEKEY_BLE_DEBUG) {
+    // eslint-disable-next-line no-console
+    console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  }
@@
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      // eslint-disable-next-line no-console
+      console.log('[NobleBLE] NOBLE bootstrap');
+    }
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron-log loaded.');
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron.ipcMain loaded.');
@@
-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    }

Also applies to: 1398-1400


1448-1449: Log byte length, not hex chars

hexData.length counts nibbles.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

1075-1090: Broaden isPairingError input and use normalized message

Prevents crashes when callbacks pass strings.

-function isPairingError(error: Error): boolean {
+function isPairingError(error: unknown): boolean {
   const keywords = [
     'authentication',
     'pairing',
     'bonding',
     'insufficient',
     'security',
     'authorization',
     'permission',
     'access denied',
   ];
-  const errorMessage = error.message.toLowerCase();
+  const errorMessage = toErrorMessage(error).toLowerCase();
   return keywords.some(k => errorMessage.includes(k));
}

Also convert write-single error paths to use toErrorMessage rather than .message.

Also applies to: 70-84


27-27: Fix comment typo

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1a9e12f and a0a61fe.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (19)
  • package.json (4 hunks)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (25 hunks)
  • packages/hd-transport-electron/src/utils/blePairing.ts (1 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
  • packages/shared/src/constants.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: lint (22)
🔇 Additional comments (15)
package.json (1)

68-71: Pending: generate lockfile & audit to validate pins

I ran npm ls sha.js cipher-base micromatch and saw:

  • No dependents for sha.js or cipher-base
  • [email protected] only under dev-deps (jest, globby, etc.)

Without a lockfile, npm audit can’t report vulnerabilities. Please:

npm install --package-lock-only
npm audit --json | jq '{advisories:.advisories,metadata:.metadata}'

Share the audit output—especially any advisories for sha.js, cipher-base, or micromatch. Then we can confirm that these pinned versions actually address Snyk’s “2 Critical” findings, and suggest targeted overrides or upgrades if needed.

packages/hd-transport-lowlevel/package.json (1)

3-3: LGTM: version alignment to alpha.7

The version bump is consistent with the monorepo changes.

packages/hd-transport-http/package.json (3)

3-3: LGTM: package version to alpha.7

Matches the coordinated release.


27-28: LGTM: internal deps aligned

hd-shared and hd-transport bump to alpha.7 look correct.


26-31: All axios dependencies upgraded with zero vulnerabilities

We’ve bumped axios to ^1.11.0 in all three packages and re-ran audits. No high- or critical-severity issues remain. Ready to merge.

• packages/hd-transport-http/package.json – axios → ^1.11.0
• packages/hd-transport-emulator/package.json – axios → ^1.11.0
• packages/core/package.json – axios → ^1.11.0

packages/hd-transport-react-native/package.json (1)

3-3: LGTM: version bump

alpha.7 aligns with sibling packages.

packages/hd-transport-emulator/package.json (3)

3-3: LGTM: package version to alpha.7

Consistent with the release train.


27-28: LGTM: internal deps aligned

hd-shared/hd-transport moved to alpha.7 as expected.


26-31: axios bump verified – no more vulnerabilities

We’ve updated axios to ^1.11.0 and reran the checks in packages/hd-transport-emulator:

All clear. Merging this change.

packages/hd-transport-electron/package.json (1)

3-3: LGTM: version bump

alpha.7 matches the monorepo push.

packages/core/package.json (1)

3-4: Good bump and clearer description.

Version aligns with the monorepo alpha.7 wave, and the description reads well.

packages/hd-ble-sdk/package.json (1)

3-3: Aligned version.

Alpha.7 bump matches the rest of the stack.

packages/hd-web-sdk/package.json (1)

3-3: Version alignment looks good.

Alpha.7 keeps this in lockstep with core/shared/transports.

packages/hd-transport-web-device/package.json (1)

3-3: Alpha.7 bump is consistent.

Matches the rest of the transport layer.

packages/connect-examples/expo-example/package.json (1)

3-3: Example version bump OK.

Keeps the sample aligned with SDK alpha.7.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/hd-transport-lowlevel/package.json (1)

1-25: Consider sideEffects and files for lean publishes

Mirror the pattern used in other packages to keep artifacts small and tree‑shakable.

 {
   "name": "@onekeyfe/hd-transport-lowlevel",
   "version": "1.1.10-alpha.8",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
   "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
packages/hd-transport-react-native/package.json (1)

1-27: Add sideEffects and files to improve publish footprint

Small change, big win for consumers.

 {
   "name": "@onekeyfe/hd-transport-react-native",
   "version": "1.1.10-alpha.8",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
   "homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
packages/hd-ble-sdk/package.json (1)

1-9: Add sideEffects/files for smaller publishes

Helps tree-shaking and cuts npm tarball size.

 {
   "name": "@onekeyfe/hd-ble-sdk",
   "version": "1.1.10-alpha.8",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-web-device/package.json (1)

1-9: Consider adding sideEffects/files for publish hygiene

Improves tree-shaking and reduces package size.

 {
   "name": "@onekeyfe/hd-transport-web-device",
   "version": "1.1.10-alpha.8",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
♻️ Duplicate comments (40)
packages/shared/package.json (1)

3-3: Hygiene: add files whitelist and mark as side-effect free

Slimmer publishes and better tree-shaking.

Apply:

   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10-alpha.8",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "package.json",
+    "README.md",
+    "LICENSE"
+  ],
packages/hd-transport-http/package.json (1)

26-31: Blocker: axios 0.27.x carries known vulns; upgrade to secure 1.x

Snyk reports 2 Critical issues. axios ^0.27.2 is a common culprit. Bump to the latest 1.x and re-run audits.

Recommended change:

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.8",
     "@onekeyfe/hd-transport": "1.1.10-alpha.8",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }

Run to confirm:

#!/bin/bash
set -euo pipefail
cd packages/hd-transport-http
npm install --package-lock-only --legacy-peer-deps
npm audit --omit=dev || true
npm view axios dist-tags --json
npm ls axios --depth=0 || true
packages/hd-transport-electron/src/noble-ble-handler.ts (14)

397-405: Targeted scan: redundant removeListener and weak error typing

Avoid remove-then-add loop; normalize errors.

Apply:

-  logger?.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
+  logger?.info('[NobleBLE] Starting targeted scan for device:', targetDeviceId);
@@
-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    noble?.on('discover', onDiscover);
@@
-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error: unknown) => {
         if (error) {
           clearTimeout(timeout);
-          if (noble) {
-            noble.removeListener('discover', onDiscover);
-          }
+          noble?.removeListener('discover', onDiscover);
           logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, String((error as any)?.message ?? error)));
           return;
         }

Also applies to: 414-433, 436-449


661-686: Force reconnect: avoid .message on unknown errors

Normalize error first.

Apply:

-    peripheral.connect((error: Error | undefined) => {
+    peripheral.connect((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${String((error as any)?.message ?? error)}`));

947-975: Connect path: unknown error typing and normalized messages

Prevent .message on non-Error; ensure disconnect on discovery failure stays.

Apply:

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, String((error as any)?.message ?? error)));
         return;
       }
@@
-      } catch (discoveryError) {
+      } catch (discoveryError: any) {

21-21: Type imports from @stoprocent/noble may be missing; add shim or import from 'noble'

Some builds of @stoprocent/noble don’t ship TS types. Import types from 'noble' or add a local module declaration re-exporting them.

Apply one:

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

Or add a d.ts:

// types/stoprocent-noble.d.ts
declare module '@stoprocent/noble' {
  export * from 'noble';
  const noble: any;
  export default noble;
}

27-27: Typo in comment

“bundlinpissues” → “bundling issues”.

Apply:

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

100-105: Cut noise: move notification logs to debug

Per-notification info logs are noisy and can hurt perf. Use debug.

Apply:

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
@@
-    logger?.info('[NobleBLE] Initialized new packet state for device:', deviceId);
+    safeLog(logger, 'debug', '[NobleBLE] Init packet state', { deviceId });

Also applies to: 111-111


578-606: Service/characteristic discovery: treat callback error as unknown and normalize

Don’t assume Error. Avoid .message on non-Error.

Apply:

-      ONEKEY_SERVICE_UUIDS,
-      (error: Error | undefined, services: Service[]) => {
+      ONEKEY_SERVICE_UUIDS,
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, String((error as any)?.message ?? error)));
           return;
         }
@@
-          [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          [ONEKEY_WRITE_CHARACTERISTIC_UUID, ONEKEY_NOTIFY_CHARACTERISTIC_UUID],
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, String((error as any)?.message ?? error)));
               return;
             }

Also applies to: 607-647, 649-653


732-735: Fresh connect: same error normalization

Avoid assuming Error.

Apply:

-      freshPeripheral.connect((error: Error | undefined) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${String((error as any)?.message ?? error)}`));

1108-1123: Chunk writes: don’t log full payloads; use with-response; normalize errors

Trim logs and pass withoutResponse=false.

Apply:

-  logger?.info('[NobleBLE] Splitting into chunks:', {
+  logger?.info('[NobleBLE] Splitting into chunks:', {
@@
-      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
+      const preview = chunk.toString('hex').slice(0, 32);
+      logger?.info(`[NobleBLE] Writing chunk ${chunkIndex}:`, {
         deviceId,
         chunkIndex,
         chunkSize: chunk.length,
-        chunkData: chunk.toString('hex'),
-        withResponse: true,
+        chunkPreview: `${preview}...`,
+        withoutResponse: false,
       });
@@
-            writeCharacteristic.write(chunk, true, (error: Error | undefined) => {
+            writeCharacteristic.write(chunk, false, (error: unknown) => {
               if (error) {
                 logger?.error(`[NobleBLE] Chunk ${chunkIndex} write failed:`, error);
                 reject(
                   ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, error.message)
                 );
                 return;
               }
               resolve();
             });

Also applies to: 1125-1145


1196-1203: Subscribe: guard while unsubscribing to avoid races

Wait if an unsubscribe is in-flight before starting a new subscribe.

Apply:

-  const opState = subscriptionOperations.get(deviceId);
+  const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
@@
+  if (opState === 'unsubscribing') {
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

Also applies to: 1204-1209


1252-1264: Rebuild subscription: handle unknown errors in callbacks

Normalize subscribe/unsubscribe callback errors.

Apply:

-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) return reject(new Error(String((error as any)?.message ?? error)));
+        resolve();
+      });
+    });
     await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) {
+          reject(new Error(String((error as any)?.message ?? error)));
           return;
         }
         resolve();
       });
     });

1294-1313: Gate early console logs behind a debug flag or switch to logger

Avoid stray console logs in production.

Apply:

-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  if (process.env.ONEKEY_BLE_DEBUG) {
+    // eslint-disable-next-line no-console
+    console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  }
@@
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] NOBLE bootstrap');
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron-log loaded.');
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron.ipcMain loaded.');
@@
-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    }

1361-1361: Log bytes, not hex chars

hexData.length counts nibbles. Divide by 2.

Apply:

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

82-82: Make connect timeout configurable

3 seconds is too short, especially on Windows where pairing can take up to 30 seconds. Pull this value from an environment variable and default to 30000 ms to avoid spurious timeouts.

• File: packages/hd-transport-electron/src/noble-ble-handler.ts
• Line: 82

Apply:

- const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+ const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 30000); // default to 30 s
packages/hd-web-sdk/package.json (1)

3-3: Publish hygiene: add files and sideEffects

Keep the package lean and tree-shakeable.

Apply:

   "name": "@onekeyfe/hd-web-sdk",
   "version": "1.1.10-alpha.8",
   "author": "OneKey",
+  "sideEffects": false,
+  "files": [
+    "build/**/*",
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/connect-examples/expo-playground/package.json (2)

3-3: Confirm 1.1.10-alpha.8 is published before merging

Pinned example app versions often break installs if tags aren’t on npm. Please verify alpha.8 exists for all three internal SDK packages.

Run:

#!/bin/bash
set -euo pipefail
ver="1.1.10-alpha.8"
for p in "@onekeyfe/hd-core" "@onekeyfe/hd-shared" "@onekeyfe/hd-web-sdk"; do
  echo -n "$p@$ver => "
  npm view "$p@$ver" version --silent || echo "NOT PUBLISHED"
done

20-22: Consider workspace protocol for local dev; keep pins only if you guarantee publish

If this example is meant to run from the monorepo, use workspaces. If it must install from npm, keep pins but ensure they are published.

-    "@onekeyfe/hd-core": "1.1.10-alpha.8",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-web-sdk": "1.1.10-alpha.8",
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-web-sdk": "workspace:*",
packages/hd-transport/package.json (1)

1-31: Add sideEffects and files to slim the publish and improve tree‑shaking

This keeps the npm tarball small and signals safe tree-shaking.

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10-alpha.8",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
   "author": "OneKey",
packages/hd-transport-lowlevel/package.json (1)

22-24: Use workspace protocol for internal deps to reduce churn

Exact prerelease pins are brittle across the monorepo. Workspaces simplify bumps.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.8"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }
packages/hd-transport-react-native/package.json (1)

22-23: Switch internal deps to workspaces to reduce manual bumps

Keeps RN transport in lockstep with shared/transport without editing pins each time.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.8",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
     "@onekeyfe/react-native-ble-utils": "^0.1.4",
     "react-native-ble-plx": "3.5.0"
   }
packages/hd-transport-electron/package.json (4)

23-26: Declare Linux HCI backend as an optional peer for better DX

Most Linux users need @stoprocent/bluetooth-hci-socket. Declare it as an optional peer to guide installs without breaking macOS/Windows.

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

16-21: Rebuild native modules against Electron to avoid ABI mismatch

@noble’s stack may include native addons. Add a postinstall rebuild to prevent runtime load errors.

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
     "lint": "eslint .",
-    "lint:fix": "eslint . --fix"
+    "lint:fix": "eslint . --fix",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket"
   },

Also add the tool:

   "devDependencies": {
     "@types/web-bluetooth": "^0.0.17",
     "electron": "^25.0.0",
+    "electron-rebuild": "^3.6.0",
     "typescript": "^5.3.3"
   }

Please run your Electron example to verify the addon loads without NODE_MODULE_VERSION errors.


28-33: Use workspace protocol for internal packages to reduce churn

This keeps the monorepo in sync and avoids unpublished pins during development.

-    "@onekeyfe/hd-core": "1.1.10-alpha.8",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.8",
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",

31-31: Allow patch updates for noble

Use a caret so you pick up critical fixes automatically.

-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
packages/core/package.json (2)

1-9: Improve publish hygiene: mark side-effect-free and limit published files

This helps tree-shaking and reduces package size.

 {
   "name": "@onekeyfe/hd-core",
   "version": "1.1.10-alpha.8",
   "description": "Core processes and APIs for communicating with OneKey hardware devices.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],

28-31: Switch internal deps to workspace protocol; bump axios to a supported major

  • Use workspace:* for local cohesion.
  • axios 0.27.x is dated and may trigger security tools. Move to 1.x if possible.
-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.8",
-    "axios": "^0.27.2",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
+    "axios": "^1.7.4",

Please run your tests and Snyk/audit to confirm no breaking changes or open CVEs remain.

packages/hd-ble-sdk/package.json (1)

23-26: Adopt workspace protocol for internal deps

Keeps the monorepo aligned and avoids reliance on unpublished alphas during dev.

-    "@onekeyfe/hd-core": "1.1.10-alpha.8",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.8"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
packages/hd-transport-web-device/package.json (2)

23-25: Use workspace protocol for internal deps

Prevents 404s when alpha tags aren’t yet published.

-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.8"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"

27-27: DevDependency can also use workspace

Keeps local examples consistent.

-    "@onekeyfe/hd-transport-electron": "1.1.10-alpha.8",
+    "@onekeyfe/hd-transport-electron": "workspace:*",
packages/connect-examples/expo-example/package.json (1)

10-10: Use a single cross-env

One cross-env is enough to set multiple vars.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
packages/hd-common-connect-sdk/package.json (3)

3-3: Add CHANGELOG entry and tag for 1.1.10-alpha.8

Version bump is fine. Please add release notes and push a matching git tag to keep bookkeeping tight.

Run:

#!/bin/bash
# Check for CHANGELOG entry (repo root and package dir)
rg -nP '^\s*##\s*\[?1\.1\.10-alpha\.8\]?' -C2 --glob '!**/node_modules/**' || echo "No 1.1.10-alpha.8 entry found"

# List matching tags (won't show if CI doesn't have git)
git tag -l | rg '^1\.1\.10-alpha\.8$' || echo "No 1.1.10-alpha.8 tag found"

23-28: Confirm alpha.8 is published for all internal deps

Avoid broken installs by ensuring every referenced alpha.8 exists on npm.

#!/bin/bash
set -euo pipefail
pkgs=(
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
)
for p in "${pkgs[@]}"; do
  printf "%-55s" "$p"
  npm view "$p" version >/dev/null 2>&1 && echo "published" || echo "NOT PUBLISHED"
done

23-28: Optional: switch internal deps to workspace protocol

Using workspace:* for internal packages reduces churn across the monorepo.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.8",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport-emulator": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10-alpha.8",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.8"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-emulator": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
   }
packages/connect-examples/electron-example/package.json (5)

25-26: Externalize native modules and verify asarUnpack

Webpack tends to bundle everything. Keep noble and the HCI socket external so native bindings load correctly, and make sure electron-builder asarUnpack includes them.

#!/bin/bash
set -euo pipefail
# Verify electron-builder config has proper asarUnpack entries
fd --type f 'electron-builder.config.*' packages/connect-examples/electron-example | xargs -I{} sh -c 'echo ">> {}"; rg -nP "asarUnpack.*@stoprocent/(noble|bluetooth-hci-socket)" -C2 "{}" || echo "asarUnpack entries missing"'

# Check webpack externals don’t allowlist these native modules
cfg="packages/connect-examples/electron-example/webpack.config.ts"
if [ -f "$cfg" ]; then
  echo ">> $cfg"
  rg -nP '@stoprocent/(noble|bluetooth-hci-socket)' -C3 "$cfg" || true
  rg -n 'externals' -n -C3 "$cfg" || true
else
  echo "webpack.config.ts not found in expected path"
fi

5-7: Fix package description (still outdated here)

Earlier comment said this was fixed, but it’s still “End-to-end encrypted workspaces for teams.” Please update to reflect the Electron hardware example.

   "productName": "HardwareExample",
   "executableName": "onekey-hardware-example",
   "version": "1.1.10-alpha.8",
   "author": "OneKey",
-  "description": "End-to-end encrypted workspaces for teams",
+  "description": "OneKey hardware Electron example app",

18-20: Add CI-safe build scripts that never publish

Create make:*:ci variants to avoid accidental publishes in PR builds.

   "scripts": {
     "rebuild:deps": "electron-builder install-app-deps",
-    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
-    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
+    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",

25-26: Ensure BLE native stack completeness on Linux

Explicitly depend on @stoprocent/bluetooth-hci-socket so Electron main can load the HCI native binding at runtime. Optional deps can be skipped by packagers.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.8",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",

25-26: Drop node-fetch v2; use global fetch (Electron/Node 18+)

Removes a legacy dependency and reduces native rebuilds. Replace imports with the global fetch API (or undici if you need stricter semantics).

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.8",
     "@stoprocent/noble": "2.3.4",
     "debug": "4.3.4",
     "electron-is-dev": "^3.0.1",
     "electron-log": "^5.1.5",
     "electron-updater": "^6.2.1",
     "fs-extra": "^11.2.0",
-    "node-fetch": "^2.6.7"
+    // use global fetch provided by Node/Electron; remove node-fetch
   },

Follow-up: replace any import fetch from 'node-fetch' or require('node-fetch') in code.

#!/bin/bash
rg -nP "from 'node-fetch'|require\(['\"]node-fetch['\"]\)" -C2 packages || echo "No node-fetch imports found"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a0a61fe and eba2dfb.

📒 Files selected for processing (17)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (25 hunks)
  • packages/hd-transport-electron/src/utils/windowsBleRecovery.ts (1 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Publish v1.1.10-alpha.8
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
  • GitHub Check: Socket Security: Pull Request Alerts
🔇 Additional comments (11)
packages/hd-web-sdk/package.json (1)

24-27: All internal alpha.8 dependencies confirmed published

No install blockers remain. Ready to merge.

packages/hd-transport/package.json (1)

3-4: LGTM on version and description

Version aligns with the repo and the description is clear.

packages/hd-transport-lowlevel/package.json (1)

3-3: Version bump looks good

No issues spotted with the metadata change.

packages/hd-transport-emulator/package.json (1)

3-3: LGTM on package version

No concerns with the version alignment.

packages/hd-transport-react-native/package.json (1)

3-3: Version alignment OK

No functional concerns with the bump.

packages/core/package.json (2)

7-7: License consistency check

Other packages use MIT; this one uses ISC. Confirm the choice is intentional and aligned with org policy.


28-29: Sanity check passed: internal packages are published

Both @onekeyfe/[email protected] and @onekeyfe/[email protected] are available on npm. You can safely merge.

packages/hd-ble-sdk/package.json (1)

23-26: Alpha.8 versions published
All three internal dependencies—@onekeyfe/hd-core, @onekeyfe/hd-shared, and @onekeyfe/hd-transport-react-native—are live on npm at 1.1.10-alpha.8. Safe to merge.

packages/hd-transport-web-device/package.json (1)

23-27: All alpha.8 artifacts are published to npm

The npm view check confirms that version 1.1.10-alpha.8 exists for all four packages, so downstream installs won’t break:

No further action needed.

packages/connect-examples/expo-example/package.json (1)

22-25: All alpha.8 packages are published

I ran npm view on each of the four 1.1.10-alpha.8 packages and confirmed they’re all available on npm. The example will install cleanly for users outside the repo. You can still switch to workspace:* versions locally if you prefer.

packages/connect-examples/electron-example/package.json (1)

5-5: Action Required: Triage 2 Critical Snyk Issues in electron-example Before Merge

  • Open the Snyk report linked in this PR. Identify the exact packages and file paths flagged as Critical.
  • In packages/connect-examples/electron-example/package.json
    • node-fetch@^2.6.7 is included and has known vulnerabilities (e.g. CVE-2022-0235: exposure of sensitive data; CVSS 6.1 MEDIUM (advisories.gitlab.com), CVE-2022-2596: DoS; CVSS 5.9 MEDIUM (github.com)).
    • Remove or upgrade node-fetch to a safe version (>=3.2.10) or swap in undici/Electron’s built-in fetch.
  • Review native-packaging deps (electron, electron-builder). Upgrade or patch any flagged versions.
  • For each Critical issue, apply a fix (upgrade, replace, patch) or suppress with a clear justification.
  • Don’t merge until Snyk shows zero Critical findings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (19)
package.json (3)

33-35: Make Lerna publish non-interactive in CI

Add --yes so CI doesn’t hang on prompts.

-    "publish-packages": "yarn check-versions && lerna publish from-package",
+    "publish-packages": "yarn check-versions && lerna publish from-package --yes",

40-41: Add CI-safe desktop build variants (no publish)

Provide :ci scripts that skip publishing and point workflows to them.

   "scripts": {
@@
     "build:example:desktop:mac": "cd ./packages/connect-examples/electron-example && yarn make:mac",
     "build:example:desktop:win": "cd ./packages/connect-examples/electron-example && yarn make:win",
+    "build:example:desktop:mac:ci": "cd ./packages/connect-examples/electron-example && yarn make:mac:ci",
+    "build:example:desktop:win:ci": "cd ./packages/connect-examples/electron-example && yarn make:win:ci",

86-86: Scope electron-builder to the example app

Keep electron-builder in packages/connect-examples/electron-example/devDependencies to avoid heavy installs at the root. Update root scripts to call the workspace script.

packages/hd-transport-electron/src/noble-ble-handler.ts (16)

479-487: Normalize scan callback error; don’t assume Error

Type as unknown and stringify safely.

-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error: unknown) => {
         if (error) {
           clearTimeout(timeout);
-          if (noble) {
-            noble.removeListener('discover', onDiscover);
-          }
+          noble?.removeListener('discover', onDiscover);
           logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, String((error as any)?.message ?? error)));
           return;
         }

793-803: Fresh peripheral connect: normalize error

-      freshPeripheral.connect((error: Error | undefined) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${String((error as any)?.message ?? error)}`));
         } else {

21-21: Type imports from @stoprocent/noble may fail

Use types from 'noble' or add a local d.ts shim. Runtime can still require '@stoprocent/noble'.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

Would you like a tiny d.ts that re-exports types if you prefer keeping @stoprocent/noble?


96-101: Reduce notification log noise

Use debug to avoid spamming info logs per packet.

-  logger?.info('[NobleBLE] Notification', {
+  logger?.debug('[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

471-475: Redundant removeListener before add

Removing the same handler you’re about to add is unnecessary. Just attach the listener.

-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    noble?.on('discover', onDiscover);

744-748: Force reconnect: avoid .message on non-Error

-    peripheral.connect((error: Error | undefined) => {
+    peripheral.connect((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${String((error as any)?.message ?? error)}`));
       } else {

1010-1015: Connect callback: error typing and message safety

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, String((error as any)?.message ?? error)));
         return;
       }

1066-1101: Unsubscribe: error is unknown; always normalize

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

1128-1133: Guard subscribe against in-flight unsubscribe

Wait until the previous unsubscribe finishes to avoid races.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1178-1196: Resubscribe helpers: normalize unsubscribe/subscribe errors

Handle callback errors as unknown and convert to Error.

-  async function rebuildAppSubscription(
+  async function rebuildAppSubscription(
     deviceId: string,
     notifyCharacteristic: Characteristic
   ): Promise<void> {
     // 监听器已在上面清理,这里不需要重复清理
-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) =>
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) return reject(new Error(String((error as any)?.message ?? error)));
+        resolve();
+      }),
+    );
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(String((error as any)?.message ?? error)));
+        resolve();
+      });
+    });

1221-1234: Gate console logs behind a debug flag

Avoid unconditional console noise in production.

-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  if (process.env.ONEKEY_BLE_DEBUG) {
+    console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  }
@@
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log('[NobleBLE] NOBLE bootstrap');
+    }
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log('[NobleBLE] electron-log loaded.');
+    }
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log('[NobleBLE] electron.ipcMain loaded.');
+    }

1241-1242: Gate handler registration console log

-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    }

1290-1294: Log byte length, not hex chars

hexData.length counts nibbles. Log bytes.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

654-661: Characteristic discovery: same normalization

-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, String((error as any)?.message ?? error)));
               return;
             }

82-82: 3s connection timeout may be too short; make it configurable

Windows pairing can need longer. Gate by env with a safe default.

-const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 3000);

635-641: Service discovery: error typing and message safety

Treat error as unknown and normalize message.

-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, String((error as any)?.message ?? error)));
           return;
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eba2dfb and 6a75120.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • package.json (4 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (24 hunks)
  • packages/hd-transport-electron/src/utils/write.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: lint (22)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: build (22)
🔇 Additional comments (3)
package.json (3)

33-33: Good preflight check gate

Nice addition. Running check-versions before publish reduces release mistakes.


54-54: Dev script looks good

Scoped dev:transport-electron runner is clear and consistent with others.


68-70: Verify lockfile overrides

  • Root package.json’s resolutions pin
    • micromatch→4.0.8
    • sha.js→2.4.12
    • cipher-base→1.0.5
  • yarn.lock shows those versions locked.
  • No workspace package declares these deps directly—no conflicts.

Please run Snyk and your full test suite to confirm these pins clear the 2 critical vulnerabilities and don’t break any transitive dependencies.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (18)
packages/hd-transport-electron/src/utils/write.ts (5)

121-134: Fix timeout: clear timer to avoid unhandled rejection

Promise.race leaves the timeout pending; when it fires it can reject unhandled. Clear it on settle.

-async function writeWithTimeout(
-  doWrite: () => Promise<void>,
-  timeoutMs: number = RETRY_CONFIG.WRITE_TIMEOUT
-): Promise<void> {
-  const timeoutPromise = new Promise<never>((_, reject) => {
-    setTimeout(() => {
-      reject(
-        ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, `Write timeout after ${timeoutMs}ms`)
-      );
-    }, timeoutMs);
-  });
-
-  return Promise.race([doWrite(), timeoutPromise]);
-}
+async function writeWithTimeout(
+  doWrite: () => Promise<void>,
+  timeoutMs: number = RETRY_CONFIG.WRITE_TIMEOUT
+): Promise<void> {
+  return new Promise<void>((resolve, reject) => {
+    const timer = setTimeout(() => {
+      reject(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, `Write timeout after ${timeoutMs}ms`));
+    }, timeoutMs);
+    doWrite()
+      .then(() => resolve())
+      .catch(reject)
+      .finally(() => clearTimeout(timer));
+  });
+}

257-265: Windows retry: log says “wait” but code doesn’t wait

Insert the 3s backoff between attempts per the PR intent (10 × 3s = 30s).

       logger?.info('[BLE-Write] Waiting before retry', {
         deviceId,
         attempt,
         context: contextLabel,
       });
 
-      // 继续下一次重试
+      // 继续下一次重试
+      await wait(3000);

280-295: Noble write flag inverted (withoutResponse vs withResponse)

Use withoutResponse=true only for chunked writes. Single packets should expect a response.

-  const doWrite = (): Promise<void> =>
-    new Promise((resolve, reject) => {
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+  const doWrite = (): Promise<void> =>
+    new Promise((resolve, reject) => {
+      // Chunked writes: withoutResponse=true; single: false
+      const withoutResponse = options?.chunkIndex !== undefined;
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
           const errorMessage =
             options?.chunkIndex !== undefined
               ? `Chunk ${options.chunkIndex} write failed: ${error}`
               : `Single packet write failed: ${error}`;
 
           logger?.error(`[NobleBLE] ${errorMessage}`);
           reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, String(error)));
           return;
         }
         resolve();
       });
     });

299-307: Windows (paired) path should honor timeout

Even paired devices can stall. Wrap direct writes with the same timeout helper.

-      return doWrite();
+      return writeWithTimeout(doWrite, options?.timeoutMs ?? RETRY_CONFIG.WRITE_TIMEOUT);

320-322: Non-Windows path should honor timeout

Keep behavior consistent across platforms.

-  // 其他平台: 直接写入
-  return doWrite();
+  // 其他平台: 带超时的直接写入
+  return writeWithTimeout(doWrite, options?.timeoutMs ?? RETRY_CONFIG.WRITE_TIMEOUT);
packages/hd-transport-electron/src/noble-ble-handler.ts (13)

21-21: Type imports from @stoprocent/noble may be missing

Prefer importing types from 'noble' or add a local d.ts shim.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

32-32: Fix typo in comment

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

102-107: Reduce notification log noise

Use debug level for per-notification logs.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

478-482: Targeted scan: remove redundant removeListener; normalize error typing

Avoid unnecessary removeListener before add; handle error as unknown and normalize.

-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    // Attach discover listener for target device
+    noble?.on('discover', onDiscover);
@@
-    if (noble) {
-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+    if (noble) {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error: unknown) => {
         if (error) {
           clearTimeout(timeout);
-          if (noble) {
-            noble.removeListener('discover', onDiscover);
-          }
+          noble?.removeListener('discover', onDiscover);
           logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, toErrorMessage(error)));
           return;
         }

Also add the import:

-import { safeLog } from './types/noble-extended';
+import { safeLog, toErrorMessage } from './types/noble-extended';

Also applies to: 486-499


642-648: Service/characteristic discovery: error should be unknown; normalize message

Prevents crashes on string errors.

-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
           return;
         }
@@
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
               return;
             }

Also applies to: 664-668


752-755: Force reconnect: normalize error before constructing message

Avoids .message on non-Error.

-        logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        logger?.error('[NobleBLE] Force reconnect failed:', error);
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));

801-804: Fresh peripheral connect: normalize error

Same issue.

-        if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+        if (error) {
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));

1017-1022: Connect callback: type unknown; normalize error

Safer error handling.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }

1073-1107: Unsubscribe: error is unknown; always normalize

Keep logs/messages consistent.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', toErrorMessage(error));
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

1184-1203: Rebuild subscription: treat callback errors as unknown; normalize

Avoid unsafe .message access.

-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });

1135-1141: Guard subscribe against in-flight unsubscribe

Wait until op state returns to idle to avoid races.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    // Wait until unsubscribe completes
+    // eslint-disable-next-line no-await-in-loop
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1233-1247: Gate console logs behind debug; prefer logger

Reduce noise in production.

-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
@@
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] NOBLE bootstrap');
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron-log loaded successfully.');
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron.ipcMain loaded successfully.');
@@
-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    }

Also applies to: 1286-1288, 1360-1361


1336-1342: IPC write log: report bytes, not hex chars

hexData.length is nibbles. Divide by 2.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6a75120 and 89fdc64.

📒 Files selected for processing (2)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (25 hunks)
  • packages/hd-transport-electron/src/utils/write.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (2)
packages/hd-transport-electron/src/noble-ble-handler.ts (2)

991-993: Small wait before discovery: good call

The 300ms settle helps avoid flaky discovery after immediate connects.


589-603: Listener cleanup utility: solid

Clears Noble listeners defensively and logs errors. This reduces leak risk.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (4)
packages/hd-transport-electron/src/utils/write.ts (4)

103-116: Fix timeout helper to clear timer (regression).

Promise.race leaves a dangling timer that can later reject. Clear it on settle.

-async function writeWithTimeout(
-  doWrite: () => Promise<void>,
-  timeoutMs: number = RETRY_CONFIG.WRITE_TIMEOUT
-): Promise<void> {
-  const timeoutPromise = new Promise<never>((_, reject) => {
-    setTimeout(() => {
-      reject(
-        ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, `Write timeout after ${timeoutMs}ms`)
-      );
-    }, timeoutMs);
-  });
-
-  return Promise.race([doWrite(), timeoutPromise]);
-}
+async function writeWithTimeout(
+  doWrite: () => Promise<void>,
+  timeoutMs: number = RETRY_CONFIG.WRITE_TIMEOUT
+): Promise<void> {
+  return new Promise<void>((resolve, reject) => {
+    const timer = setTimeout(() => {
+      reject(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, `Write timeout after ${timeoutMs}ms`));
+    }, timeoutMs);
+    doWrite()
+      .then(resolve)
+      .catch(reject)
+      .finally(() => clearTimeout(timer));
+  });
+}

131-212: Add 3s wait between Windows attempts.

You retry but never pause. Insert the 3s delay to match the pairing strategy.

-    }
+    }
+    // Wait 3s between attempts (Windows pairing)
+    if (attempt < maxAttempts) {
+      logger?.debug('[BLE-Write] Waiting before retry', { deviceId, attempt, maxAttempts, context: contextLabel });
+      await wait(3000);
+    }

225-240: Invert Noble write flag: use withoutResponse (not withResponse).

Noble API is write(data, withoutResponse, cb). Single packet should request a response; chunks can skip it.

-  const doWrite = (): Promise<void> =>
-    new Promise((resolve, reject) => {
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+  const doWrite = (): Promise<void> =>
+    new Promise((resolve, reject) => {
+      const withoutResponse = options?.chunkIndex !== undefined; // chunks: no response; single: with response
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {

265-267: Honor timeout on non-Windows too.

Keep behavior consistent across platforms.

-  // 其他平台: 直接写入
-  return doWrite();
+  // 其他平台: 带超时的直接写入
+  return writeWithTimeout(doWrite, options?.timeoutMs);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 89fdc64 and 1b2a2d9.

📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/utils/write.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (2)
packages/hd-transport-electron/src/utils/write.ts (2)

139-142: Use a disconnection-specific error code (not BleConnectedError).

These branches represent disconnects or failed rebuilds, not “connected” errors. Switch to the appropriate code from hd-shared.

Would you confirm the correct constant (e.g., BleDisconnected/BleDeviceNotConnected) in @onekeyfe/hd-shared and replace here?

Also applies to: 203-206


39-76: Good DI for connection/pairing/rebuild hooks.

Clear separation and injectability make this easy to test and evolve.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

♻️ Duplicate comments (24)
packages/connect-examples/expo-playground/package.json (1)

20-22: Avoid brittle exact pins in example app.

Use workspace protocol (for local dev) or a caret range. Exact pins break if 1.1.10 isn’t on npm yet.

Apply one of:

-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-web-sdk": "1.1.10",
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-web-sdk": "workspace:^",

or:

-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-web-sdk": "1.1.10",
+    "@onekeyfe/hd-core": "^1.1.10",
+    "@onekeyfe/hd-shared": "^1.1.10",
+    "@onekeyfe/hd-web-sdk": "^1.1.10",
packages/shared/package.json (1)

3-3: Add sideEffects and files whitelist for lean, tree‑shakable publishes.

Keeps the npm tarball small and helps bundlers.

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport/package.json (1)

3-4: Publish hygiene: add sideEffects and files.

Match other packages to enable tree‑shaking and smaller publishes.

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-emulator/package.json (1)

27-31: Upgrade axios to latest 1.x in all packages

We still have axios pinned at ^0.27.2 in three packages. Versions in the 0.27.x line carry SSRF/CSRF vulnerabilities. Please bump them to at least 1.8.2 (ideally the latest 1.x, e.g. ^1.11.0).

Files to update:

  • packages/hd-transport-http/package.json
  • packages/hd-transport-emulator/package.json
  • packages/core/package.json

Suggested diffs:

--- packages/hd-transport-http/package.json
@@ dependencies
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
--- packages/hd-transport-emulator/package.json
@@ dependencies
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
--- packages/core/package.json
@@ dependencies
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
packages/hd-transport-react-native/package.json (1)

22-23: Use workspace protocol for internal deps (or ensure 1.1.10 is published).

Reduces churn on future bumps and avoids broken installs.

-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
packages/hd-ble-sdk/package.json (1)

23-25: Verify internal pins are published or switch to workspaces.

Pins to 1.1.10 look fine if published. Otherwise use the workspace protocol to avoid broken installs.

Run to verify publish status:

#!/bin/bash
set -euo pipefail
for p in \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]"
do
  printf "%-45s" "$p"
  npm view "$p" version >/dev/null 2>&1 && echo "published" || echo "NOT PUBLISHED"
done

Optional workspace switch:

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-react-native": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }
packages/core/package.json (1)

3-5: Add files/sideEffects and track release (CHANGELOG + tag).

Small polish that helps consumers find changes and trim bundles.

   "version": "1.1.10",
   "description": "Core processes and APIs for communicating with OneKey hardware devices.",
+  "sideEffects": false,
+  "files": ["dist/**/*","README.md","LICENSE","package.json"],

Check release artifacts:

#!/bin/bash
rg -nP '^\s*##\s*\[?1\.1\.10\]?' -C2 --glob 'CHANGELOG*' --glob '!**/node_modules/**' || echo "No CHANGELOG entry found"
git fetch --tags >/dev/null 2>&1
git tag -l | rg '^v?1\.1\.10$' || echo "No git tag named 1.1.10"
packages/hd-transport-electron/src/utils/write.ts (6)

210-225: Fix Noble write flag: pass withoutResponse, not withResponse.

Current code always uses withoutResponse=true, which hides link errors for single packets.

-  const doWrite = (): Promise<void> =>
-    new Promise((resolve, reject) => {
-      writeCharacteristic.write(buffer, true, (error: Error | undefined) => {
+  const doWrite = (): Promise<void> =>
+    new Promise((resolve, reject) => {
+      // single packet => with response (withoutResponse=false)
+      // chunked packet => withoutResponse=true
+      const withoutResponse = options?.chunkIndex !== undefined;
+      writeCharacteristic.write(buffer, withoutResponse, (error: unknown) => {
         if (error) {
           const errorMessage =
             options?.chunkIndex !== undefined
               ? `Chunk ${options.chunkIndex} write failed: ${error}`
               : `Single packet write failed: ${error}`;
 
           logger?.error(`[NobleBLE] ${errorMessage}`);
           reject(ERRORS.TypedError(HardwareErrorCode.BleWriteCharacteristicError, String(error)));
           return;
         }
         resolve();
       });
     });

249-251: Apply timeout on non‑Windows path.

Parity with Windows and safer writes.

-  // 其他平台: 直接写入
-  return doWrite();
+  // 其他平台: 带超时的直接写入
+  return writeWithTimeout(doWrite, options?.timeoutMs);

229-237: Honor timeout for Windows “paired” fast path.

Avoid hanging writes when already paired.

-      return doWrite();
+      return writeWithTimeout(doWrite, options?.timeoutMs);

276-281: Avoid leaking payload in info logs.

Gate firstBytes behind debug or mask in production.

-  logger?.info('[NobleBLE] Writing data:', {
+  logger?.debug?.('[NobleBLE] Writing data:', {
     deviceId,
     dataLength: buffer.length,
     firstBytes: buffer.subarray(0, 8).toString('hex'),
   });

301-301: Normalize logger meta shape.

Pass an object for consistency.

-  logger?.info('[NobleBLE] Splitting into chunks:', chunks.length);
+  logger?.info('[NobleBLE] Splitting into chunks:', { count: chunks.length });

100-112: Reintroduce a safe timeout helper.

Used above. Clears timers to avoid unhandled rejections.

 // 取消定时器式的“超时”写入窗口,改为在循环中按固定间隔发送写入
+async function writeWithTimeout(
+  doWrite: () => Promise<void>,
+  timeoutMs: number = RETRY_CONFIG.WRITE_TIMEOUT
+): Promise<void> {
+  return new Promise<void>((resolve, reject) => {
+    const timer = setTimeout(() => {
+      reject(ERRORS.TypedError(HardwareErrorCode.BleTimeoutError, `Write timeout after ${timeoutMs}ms`));
+    }, timeoutMs);
+    doWrite()
+      .then(resolve)
+      .catch(reject)
+      .finally(() => clearTimeout(timer));
+  });
+}
packages/hd-web-sdk/package.json (2)

24-27: Confirm 1.1.10 artifacts are on npm or switch to workspaces.

Avoids broken installs for web consumers.

#!/bin/bash
set -euo pipefail
for p in \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]"
do
  printf "%-45s" "$p"
  npm view "$p" version >/dev/null 2>&1 && echo "published" || echo "NOT PUBLISHED"
done

Workspace option:

   "dependencies": {
     "@onekeyfe/cross-inpage-provider-core": "^0.0.17",
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-http": "1.1.10",
-    "@onekeyfe/hd-transport-web-device": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
   }

3-3: Add files/sideEffects for slimmer publish.

Improves tree-shaking and package size.

   "version": "1.1.10",
+  "sideEffects": false,
+  "files": [
+    "build/**/*",
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-lowlevel/package.json (1)

22-24: Workspace refs or publish verification for internal deps.

Keep monorepo bumps simple and installs reliable.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }

Or verify publish:

#!/bin/bash
for p in "@onekeyfe/[email protected]" "@onekeyfe/[email protected]"; do
  printf "%-40s" "$p"
  npm view "$p" version >/dev/null 2>&1 && echo "published" || echo "NOT PUBLISHED"
done
packages/hd-transport-http/package.json (1)

27-31: Upgrade axios to a secure 1.x line

axios ^0.27.2 has known vulns. Move to the latest 1.x and run tests.

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10",
     "@onekeyfe/hd-transport": "1.1.10",
-    "axios": "^0.27.2",
+    "axios": "^1.7.0",
     "secure-json-parse": "^4.0.0"
   }
#!/bin/bash
cd packages/hd-transport-http
npm view axios dist-tags --json
npm audit --omit=dev || true
packages/hd-transport-web-device/package.json (1)

23-27: Optional: switch internal deps to workspace protocol

Reduces version bump churn across the monorepo.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   },
   "devDependencies": {
-    "@onekeyfe/hd-transport-electron": "1.1.10",
+    "@onekeyfe/hd-transport-electron": "workspace:*",
     "@types/w3c-web-usb": "^1.0.6",
     "@types/web-bluetooth": "^0.0.17"
   }
packages/connect-examples/expo-example/package.json (1)

10-10: Use a single cross-env

One cross-env is enough.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
packages/hd-common-connect-sdk/package.json (1)

22-28: Optional: use workspace protocol for internal deps

Keeps packages in sync locally and cuts release friction.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-emulator": "1.1.10",
-    "@onekeyfe/hd-transport-http": "1.1.10",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10",
-    "@onekeyfe/hd-transport-web-device": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-emulator": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
   }
packages/connect-examples/electron-example/package.json (4)

32-32: Drop node-fetch v2; use global fetch (Node 18+/Electron 28).
Reduces deps and native rebuilds. Follow up by replacing imports in source.

-    "node-fetch": "^2.6.7"
+    // removed: rely on global fetch (or undici if needed)

19-20: Add CI-safe build scripts (avoid publishing on CI).
Mirror make:* with :ci variants that pass --publish never.

   "scripts": {
@@
     "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
     "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",

25-26: Add @stoprocent/bluetooth-hci-socket explicitly to avoid module-not-found on Linux.
Noble won’t always pull the HCI socket in your packaging flow. Declare it here.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "2.2.3",
     "debug": "4.3.4",

Run to confirm it’s declared somewhere in the repo:

#!/bin/bash
rg -n "@stoprocent/bluetooth-hci-socket" -C2

7-7: Fix misleading package description.
This is a hardware Electron example, not “end‑to‑end encrypted workspaces”.

-  "description": "End-to-end encrypted workspaces for teams",
+  "description": "OneKey hardware Electron example app",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1b2a2d9 and 0405627.

📒 Files selected for processing (16)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/utils/write.ts (1 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (6)
packages/hd-transport/package.json (2)

3-3: Version bump OK.


4-4: Description cleanup reads well.

packages/hd-transport-emulator/package.json (1)

3-3: Version bump OK.

packages/hd-transport-react-native/package.json (1)

3-3: Version bump OK.

packages/connect-examples/electron-example/package.json (2)

5-5: Version bump looks fine.


18-18: Good call prebuilding native deps for Electron.
This will prevent runtime rebuild churn for noble and friends.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/hd-transport-electron/src/noble-ble-handler.ts (2)

449-456: Targeted scan: avoid redundant removeListener, and always detach on timeout; normalize error.

   return new Promise((resolve, reject) => {
     const timeout = setTimeout(() => {
       if (noble) {
         noble.stopScanning();
+        noble.removeListener('discover', onDiscover);
       }
       logger?.info('[NobleBLE] Targeted scan timeout for device:', targetDeviceId);
       resolve(null);
     }, FAST_SCAN_TIMEOUT);
@@
-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    // Attach discover listener for this scan
+    noble?.on('discover', onDiscover);
@@
-    if (noble) {
-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+    if (noble) {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error: unknown) => {
         if (error) {
           clearTimeout(timeout);
-          if (noble) {
-            noble.removeListener('discover', onDiscover);
-          }
-          logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          noble?.removeListener('discover', onDiscover);
+          logger?.error('[NobleBLE] Failed to start targeted scan:', error);
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, toErrorMessage(error)));
           return;
         }
 
         logger?.info('[NobleBLE] Targeted scan started for device:', targetDeviceId);
       });
     }

Also applies to: 478-482, 486-499


536-541: Enumeration: normalize startScanning errors.

-    noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+    noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error: unknown) => {
       if (error) {
         clearTimeout(timeout);
-        logger?.error('[NobleBLE] Failed to start scanning:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+        logger?.error('[NobleBLE] Failed to start scanning:', error);
+        reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, toErrorMessage(error)));
         return;
       }
♻️ Duplicate comments (14)
packages/hd-transport-electron/src/noble-ble-handler.ts (14)

800-809: Fresh peripheral connect: normalize error.

-    await new Promise<void>((resolve, reject) => {
-      freshPeripheral.connect((error: Error | undefined) => {
+    await new Promise<void>((resolve, reject) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${toErrorMessage(error)}`));
         } else {

21-21: Type-only imports should come from 'noble' (runtime stays @stoprocent/noble).

@stoprocent/noble may not ship types. Import types from 'noble' to avoid build breaks; keep runtime require('@stoprocent/noble').

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

32-32: Fix typo in comment.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

100-107: Reduce notification log noise; use debug.

-  //  notification telemetry
-  logger?.info('[NobleBLE] Notification', {
+  // notification telemetry
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });
@@
-    logger?.info('[NobleBLE] Initialized new packet state for device:', deviceId);
+    safeLog(logger, 'debug', '[NobleBLE] Initialized packet state for device:', deviceId);

Also applies to: 113-114


642-648: Service/characteristic discovery: treat callback error as unknown and normalize.

-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, toErrorMessage(error)));
           return;
         }
@@
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, toErrorMessage(error)));
               return;
             }

Also applies to: 661-669


751-755: Force reconnect: normalize error.

-    peripheral.connect((error: Error | undefined) => {
+    peripheral.connect((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${toErrorMessage(error)}`));

1016-1023: Connect callback: error should be unknown; normalize for TypedError.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
 
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, toErrorMessage(error)));
         return;
       }
@@
-      } catch (discoveryError) {
+      } catch (discoveryError) {
         logger?.error(
           '[NobleBLE] Service/characteristic discovery failed after all attempts:',
           discoveryError
         );
         // Disconnect on failure
         connectedPeripheral.disconnect();
-        reject(discoveryError);
+        reject(discoveryError as unknown as Error);
       }

Also applies to: 1031-1045


1074-1108: Unsubscribe callback: type as unknown; always normalize.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', toErrorMessage(error));
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }

1128-1134: Drop hard-coded fields in subscribe context log.

-  logger?.info('[NobleBLE] Subscribe context', {
-    deviceId,
-    opStateBefore: subscriptionOperations.get(deviceId) || 'idle',
-    paired: false,
-    hasController: false,
-  });
+  logger?.info('[NobleBLE] Subscribe context', {
+    deviceId,
+    opStateBefore: subscriptionOperations.get(deviceId) || 'idle',
+  });

1135-1140: Guard against concurrent 'unsubscribing' state before subscribing.

   const opState = subscriptionOperations.get(deviceId);
   if (opState === 'subscribing') {
     // Subscription in progress; update callback and return
     notificationCallbacks.set(deviceId, callback);
     return Promise.resolve();
   }
+  if (opState === 'unsubscribing') {
+    // Wait until unsubscribe completes
+    while (subscriptionOperations.get(deviceId) === 'unsubscribing') {
+      // eslint-disable-next-line no-await-in-loop
+      await wait(50);
+    }
+  }

1189-1203: Rebuild subscription: normalize errors in callbacks.

-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
-          return;
-        }
-        resolve();
-      });
-    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) return reject(new Error(toErrorMessage(error)));
+        resolve();
+      });
+    });

1233-1237: Gate console logs behind a debug flag (or use logger.debug).

-  // Use console.log for initial logging as electron-log might not be available yet.
-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  if (process.env.ONEKEY_BLE_DEBUG) {
+    console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  }
   try {
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] NOBLE bootstrap');
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron-log loaded.');
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron.ipcMain loaded.');
@@
-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) {
+      console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    }

Also applies to: 1241-1247, 1287-1288


1336-1342: IPC write: log bytes, not hex length.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

24-25: Import the error normalizer to avoid unsafe .message access.

You use raw error.message in multiple callbacks. Pull in toErrorMessage now and apply below.

-import { safeLog } from './types/noble-extended';
+import { safeLog, toErrorMessage } from './types/noble-extended';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0405627 and 11561de.

📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (25 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (3)
packages/hd-transport-electron/src/noble-ble-handler.ts (3)

53-55: LGTM: pairedDevices tracking is clear and localized.


70-75: LGTM: removed legacy Windows watchdog comments.


991-993: Stabilization wait: good.

Short settle delay reduces flakiness after reuse.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (22)
packages/connect-examples/expo-playground/package.json (1)

20-22: Publish alpha.9 or switch to workspace protocol.

Exact pins will break installs if alpha.9 isn’t on npm. Either confirm publish or resolve from the monorepo.

Option A (monorepo-friendly):

-    "@onekeyfe/hd-core": "1.1.10-alpha.9",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.9",
-    "@onekeyfe/hd-web-sdk": "1.1.10-alpha.9",
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-web-sdk": "workspace:^",

Option B (keep pins; verify publish first):

#!/bin/bash
set -euo pipefail
for p in "@onekeyfe/hd-core" "@onekeyfe/hd-shared" "@onekeyfe/hd-web-sdk"; do
  echo -n "$p@1.1.10-alpha.9 => "
  npm view "$p@1.1.10-alpha.9" version --silent || echo "NOT PUBLISHED"
done
packages/shared/package.json (1)

3-3: Version bump looks fine; add publish hygiene (repeat).

Add sideEffects and a files whitelist to keep the package lean and tree‑shakable.

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10-alpha.9",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-web-sdk/package.json (1)

3-3: Optional: add sideEffects/files.

Slim publish and enable tree‑shaking.

   "version": "1.1.10-alpha.9",
+  "sideEffects": false,
+  "files": [
+    "build/**/*",
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport/package.json (1)

3-4: Optional: publish hygiene (repeat).

Add sideEffects and files to reduce package size and help tree‑shaking.

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10-alpha.9",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-lowlevel/package.json (2)

3-3: Version bump OK; consider workspace protocol.

Using workspace:* simplifies future lockstep bumps across internal packages.

-  "version": "1.1.10-alpha.9",
+  "version": "1.1.10-alpha.9",

And for deps below:

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.9"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }

22-23: Optional: add files/sideEffects.

Stay consistent with other packages and keep publishes slim.

   "license": "MIT",
   "main": "dist/index.js",
   "types": "dist/index.d.ts",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-http/package.json (1)

27-31: Upgrade axios to a secure 1.x line.

0.27.x is dated; move to the latest 1.x to avoid known issues and get fixes.

Apply:

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.9",
     "@onekeyfe/hd-transport": "1.1.10-alpha.9",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }

Verify:

#!/bin/bash
cd packages/hd-transport-http
npm install --package-lock-only --legacy-peer-deps
npm ls axios --depth=0 || true
npm audit --omit=dev || true
packages/hd-transport-react-native/package.json (1)

22-23: Use workspace protocol for internal deps (optional).

Reduces churn during coordinated releases.

Apply:

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.9",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
     "@onekeyfe/react-native-ble-utils": "^0.1.4",
     "react-native-ble-plx": "3.5.0"
   }
packages/hd-transport-electron/package.json (3)

28-33: Ensure native modules are rebuilt for Electron.

Without rebuilds, native addons may fail to load under Electron >=25.

Add a postinstall (top-level scripts):

"scripts": {
  "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket"
}

Quick check:

#!/bin/bash
rg -nP 'electron-rebuild|prebuild' -C1 || echo "No rebuild hooks found"

28-33: Loosen noble to allow patch fixes.

Use a caret so you get security/bug patches automatically.

Apply:

   "dependencies": {
     "@onekeyfe/hd-core": "1.1.10-alpha.9",
     "@onekeyfe/hd-shared": "1.1.10-alpha.9",
     "@onekeyfe/hd-transport": "1.1.10-alpha.9",
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
     "p-retry": "^4.6.2"
   },

28-33: Declare Linux HCI as optional peer (guides users, avoids forced installs).

Add optional peer to improve DX on Linux.

Add (top-level JSON, outside this hunk):

"peerDependencies": {
  "electron": ">=25.0.0",
  "electron-log": ">=4.0.0",
  "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
},
"peerDependenciesMeta": {
  "@stoprocent/bluetooth-hci-socket": { "optional": true }
}
packages/core/package.json (2)

28-29: Consider workspace protocol for internal deps (optional).

Cuts manual bumps across the monorepo.

Apply:

-    "@onekeyfe/hd-shared": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.9",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",

3-4: Publish hygiene: trim tarball and aid tree-shaking (optional).

Add files/sideEffects to ship only runtime artifacts.

Add (top-level JSON):

"sideEffects": false,
"files": ["dist/**/*", "README.md", "LICENSE", "package.json"]

Also applies to: 27-36

packages/hd-ble-sdk/package.json (2)

23-25: Switch internal deps to workspace protocol (optional).

Easier coordinated releases.

Apply:

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.9",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.9"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"
   }

3-3: Publish hygiene (optional).

Add files/sideEffects to keep the package lean and tree-shakable.

Add (top-level JSON):

"sideEffects": false,
"files": ["dist/**/*", "README.md", "LICENSE", "package.json"]

Also applies to: 23-25

packages/hd-transport-web-device/package.json (2)

23-24: Switch internal deps to workspace protocol

Reduces version bump churn across the monorepo.

Apply:

-    "@onekeyfe/hd-shared": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.9"
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport": "workspace:^"

27-27: Use workspace protocol for devDependency too

Keeps local builds aligned.

-    "@onekeyfe/hd-transport-electron": "1.1.10-alpha.9",
+    "@onekeyfe/hd-transport-electron": "workspace:^",
packages/connect-examples/expo-example/package.json (1)

10-10: Collapse duplicate cross-env calls

One cross-env is enough.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
packages/hd-common-connect-sdk/package.json (1)

23-28: Prefer workspace protocol for internal packages

This cuts maintenance overhead for synchronized releases.

-    "@onekeyfe/hd-core": "1.1.10-alpha.9",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport-emulator": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10-alpha.9",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.9"
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport-emulator": "workspace:^",
+    "@onekeyfe/hd-transport-http": "workspace:^",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:^",
+    "@onekeyfe/hd-transport-web-device": "workspace:^"
packages/connect-examples/electron-example/package.json (3)

25-26: Explicitly depend on @stoprocent/bluetooth-hci-socket

Avoid “module not found” on Linux when noble loads native HCI.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.9",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",

25-26: Consider dropping node-fetch v2 (Node 18+ has fetch built-in)

Simplify deps; use global fetch or undici if needed.


18-20: Add CI-safe build scripts (no publish)

Create make:*:ci variants that never publish.

   "scripts": {
@@
-    "rebuild:deps": "electron-builder install-app-deps",
-    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
-    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "rebuild:deps": "electron-builder install-app-deps",
+    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
+    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 11561de and 2cd1c3c.

📒 Files selected for processing (15)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Publish v1.1.10-alpha.9
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
🔇 Additional comments (10)
packages/connect-examples/expo-playground/package.json (1)

3-3: Version bump looks good.

Alpha.9 aligns with the repo-wide update.

packages/hd-web-sdk/package.json (1)

8-9: Verify types output path

Your package.json sets "main": "build/onekey-js-sdk.js" and "types": "dist/index.d.ts". The build in this sandbox failed, so please run locally:

cd packages/hd-web-sdk
yarn build
ls dist/index.d.ts

and confirm that dist/index.d.ts is produced. If your d.ts files land under build/ instead, update the "types" field to match.

packages/hd-transport/package.json (1)

3-4: Nice cleanup.

Version bump and description read well.

packages/hd-transport-http/package.json (1)

3-3: Version alignment looks good.

Package and internal deps consistently bumped to 1.1.10-alpha.9.

Also applies to: 27-28

packages/hd-transport-react-native/package.json (1)

3-3: Bump looks consistent.

Version and internal deps aligned to 1.1.10-alpha.9.

Also applies to: 22-23

packages/hd-transport-electron/package.json (1)

3-3: Good dependency alignment with the BLE refactor.

Version bump and new deps match the noble switch and retry logic.

Also applies to: 28-33

packages/core/package.json (2)

3-4: Nice: real description and version bump.

Clearer metadata helps consumers.


27-36: Upgrade axios to ^1.11.0 in packages/core

Keeping all packages on the latest secure versions helps us maintain a strong security posture.

• File: packages/core/package.json
• Section: dependencies (around lines 27–36)

Apply this diff:

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.9",
     "@onekeyfe/hd-transport": "1.1.10-alpha.9",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "bignumber.js": "^9.0.2",
     "bytebuffer": "^5.0.1",

After you update, please verify the change and confirm no new issues:

cd packages/core
npm install --legacy-peer-deps
npm ls axios --depth=0
npm audit --omit=dev
packages/hd-ble-sdk/package.json (1)

3-3: Versions aligned across BLE SDK and deps.

All bumped to 1.1.10-alpha.9. Looks good.

Also applies to: 23-25

packages/hd-common-connect-sdk/package.json (1)

23-28: Verified: All pinned 1.1.10-alpha.9 versions are published

We ran npm view <pkg>@1.1.10-alpha.9 on each dependency listed in package.json. All six returned “published,” so there’s no risk of 404s downstream.

• @onekeyfe/[email protected] – published
• @onekeyfe/[email protected] – published
• @onekeyfe/[email protected] – published
• @onekeyfe/[email protected] – published
• @onekeyfe/[email protected] – published
• @onekeyfe/[email protected] – published

No further action needed here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/hd-transport-electron/src/noble-ble-handler.ts (2)

785-818: Enumeration scan: callback error typing and normalization

-    noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+    noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: unknown) => {
       if (error) {
         clearTimeout(timeout);
         logger?.error('[NobleBLE] Failed to start scanning:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, String(error)));
         return;
       }

1426-1483: DRY: reuse softRefreshSubscription instead of inline rebuildAppSubscription

You already import softRefreshSubscription. Remove the local helper and call the shared function to keep behavior consistent.

-  // Helper: rebuild a clean application-layer subscription
-  async function rebuildAppSubscription(
-    deviceId: string,
-    notifyCharacteristic: Characteristic
-  ): Promise<void> { /* ... */ }
-
-  await rebuildAppSubscription(deviceId, notifyCharacteristic);
-  subscribedDevices.set(deviceId, true);
-  subscriptionOperations.set(deviceId, 'idle');
+  await softRefreshSubscription({
+    deviceId,
+    notifyCharacteristic,
+    subscriptionOperations,
+    subscribedDevices,
+    pairedDevices,
+    notificationCallbacks,
+    processNotificationData,
+    logger,
+  });
♻️ Duplicate comments (15)
packages/hd-transport-electron/src/noble-ble-handler.ts (15)

83-83: Make connection timeout configurable (default stays 3s)

Expose via env so ops can tune relative to 30s Windows pairing window.

-const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 3000);

Please confirm 3s is acceptable with the 30s pairing probe.


695-748: Targeted scan: avoid redundant removeListener; normalize startScanning error

-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    // Attach discover listener for the target device
+    noble?.on('discover', onDiscover);
@@
-    if (noble) {
-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+    if (noble) {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: unknown) => {
         if (error) {
           clearTimeout(timeout);
-          if (noble) {
-            noble.removeListener('discover', onDiscover);
-          }
+          noble?.removeListener('discover', onDiscover);
           logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, String(error)));
           return;
         }

1266-1299: Connect callback: error typing and normalization

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
       clearTimeout(timeout);
       if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, String(error)));
         return;
       }

21-21: Types from @stoprocent/noble may not exist

Import types from 'noble' (or your local shim) while keeping runtime require('@stoprocent/noble'). This avoids broken type resolution.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

27-27: Typo: “bundlinpissues” → “bundling issues”

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

106-111: Reduce notification log noise to debug

Per-packet info logs are noisy and can hurt perf.

-  logger?.info('[NobleBLE] Notification', {
+  safeLog(logger, 'debug', '[NobleBLE] Notification', {
     deviceId,
     dataLength: data.length,
   });

838-851: Simplify Noble listener teardown and guard API

-function cleanupNobleListeners(): void {
-  if (!noble) return;
-
-  // 移除所有监听器以防止内存泄漏
-  // Noble 使用 EventEmitter,需要使用 removeAllListeners
-  try {
-    (noble as any).removeAllListeners('discover');
-    (noble as any).removeAllListeners('stateChange');
-    logger?.info('[NobleBLE] All Noble listeners cleaned up');
-  } catch (error) {
-    logger?.error('[NobleBLE] Failed to clean up some listeners:', error);
-  }
-}
+function cleanupNobleListeners(): void {
+  if (!noble) return;
+  try {
+    const ee = noble as unknown as { removeAllListeners?: (event?: string) => void };
+    ee.removeAllListeners?.();
+    logger?.info('[NobleBLE] All Noble listeners cleaned up');
+  } catch (error) {
+    logger?.error('[NobleBLE] Failed to clean up some listeners:', error);
+  }
+}

891-966: Service/characteristic discovery: treat callback error as unknown and normalize

-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, String(error)));
           return;
         }
@@
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, String(error)));
               return;
             }

999-1004: Force reconnect: avoid unsafe .message

-        logger?.error('[NobleBLE] Force reconnect failed:', error);
-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        logger?.error('[NobleBLE] Force reconnect failed:', error);
+        reject(new Error(`Force reconnect failed: ${String(error)}`));

1059-1062: Fresh peripheral connect: avoid unsafe .message

-        if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+        if (error) {
+          reject(new Error(`Fresh peripheral connection failed: ${String(error)}`));

1330-1364: Unsubscribe: error as unknown; always reset state

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
+        logger?.error('[NobleBLE] Notification unsubscription failed:', error);
       } else {
         logger?.info('[NobleBLE] Notification unsubscription successful');
       }
       // Remove all listeners and clear subscription status
       notifyCharacteristic.removeAllListeners('data');
       notificationCallbacks.delete(deviceId);
       devicePacketStates.delete(deviceId);
       subscribedDevices.delete(deviceId);
       // 🔒 Clear operation state
       subscriptionOperations.set(deviceId, 'idle');
       resolve();
     });
   });

1384-1389: Drop hard-coded fields in subscribe context log

-  logger?.info('[NobleBLE] Subscribe context', {
-    deviceId,
-    opStateBefore: subscriptionOperations.get(deviceId) || 'idle',
-    paired: false,
-    hasController: false,
-  });
+  logger?.info('[NobleBLE] Subscribe context', {
+    deviceId,
+    opStateBefore: subscriptionOperations.get(deviceId) || 'idle',
+  });

1489-1508: Replace console logs with gated or logger-based output

-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
@@
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] NOBLE bootstrap');
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron-log loaded.');
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron.ipcMain loaded.');
@@
-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);

1556-1558: IPC write log: report bytes, not hex chars

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

374-440: Implement best-effort cancel when cancelOperations=true

Currently a no-op for in-flight notifications. Unsubscribe and clear listeners before deleting maps.

   // 1. 清理设备状态
-  if (cleanupConnection) {
+  if (cleanupConnection) {
+    if (cancelOperations) {
+      const ch = deviceCharacteristics.get(deviceId);
+      try {
+        ch?.notify.removeAllListeners('data');
+        ch?.notify.unsubscribe?.(() => {});
+      } catch (e) {
+        safeLog(logger, 'warn', '[NobleBLE] Best-effort unsubscribe failed during cleanup', { deviceId, error: String(e) });
+      }
+    }
     connectedDevices.delete(deviceId);
     deviceCharacteristics.delete(deviceId);
     notificationCallbacks.delete(deviceId);
     devicePacketStates.delete(deviceId);
     subscribedDevices.delete(deviceId);
     subscriptionOperations.delete(deviceId);
     pairedDevices.delete(deviceId); // 清理windows配对状态
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2cd1c3c and 9ad546e.

📒 Files selected for processing (2)
  • packages/hd-transport-electron/src/ble-ops.ts (1 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (28 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (22)
packages/shared/package.json (1)

3-3: Publish hygiene: add sideEffects/files (repeat).
Same suggestion as before to slim the package and enable tree‑shaking.

Apply:

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10-alpha.10",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-web-sdk/package.json (1)

3-3: Optional: add sideEffects/files for slimmer publishes (repeat).
Matches prior guidance; improves tree‑shaking.

   "version": "1.1.10-alpha.10",
+  "sideEffects": false,
+  "files": [
+    "build/**/*",
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport/package.json (1)

3-4: Optional: add sideEffects/files for lean publishes (repeat).
Same nitpick as before across packages.

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10-alpha.10",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-emulator/package.json (1)

29-29: Upgrade axios to 1.x across the repo

Axios 0.27.x has known advisories. Bump to the latest 1.x, then regenerate your lockfile, audit, and test.

Affected files:

  • packages/hd-transport-emulator/package.json (line 29)
  • packages/hd-transport-http/package.json (line 29)
  • packages/core/package.json (line 30)
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",

After bump:

  • Run npm i --package-lock-only to create/update your lockfile.
  • Run npm audit --omit=dev to verify no new advisories.
  • Run the full test suite to confirm nothing breaks.
packages/hd-transport-http/package.json (1)

27-31: Upgrade axios to 1.x (0.27.2 has known vulns).

Bump axios to a patched 1.x release and run tests/audit.

   "dependencies": {
     "@onekeyfe/hd-shared": "1.1.10-alpha.10",
     "@onekeyfe/hd-transport": "1.1.10-alpha.10",
-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
     "secure-json-parse": "^4.0.0"
   }
#!/bin/bash
cd packages/hd-transport-http
npm install --package-lock-only --legacy-peer-deps
npm audit --omit=dev || true
npm ls axios --depth=0 || true
packages/core/package.json (1)

28-29: Ensure alpha.10 internal deps are published (or use workspaces).

If not published, installs break. Prefer workspace protocol in monorepo.

#!/bin/bash
for p in "@onekeyfe/[email protected]" "@onekeyfe/[email protected]"; do
  echo -n "$p => "; npm view "$p" version >/dev/null 2>&1 && echo "published" || echo "NOT PUBLISHED"
done
packages/hd-transport-lowlevel/package.json (2)

22-24: Switch internal deps to workspace protocol.

Simplifies future bumps and avoids unpublished-version breaks.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.10"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }

3-4: Add publish hygiene (sideEffects/files).

Helps tree-shaking and smaller publishes.

 {
   "name": "@onekeyfe/hd-transport-lowlevel",
   "version": "1.1.10-alpha.10",
+  "sideEffects": false,
+  "files": ["dist/**/*","README.md","LICENSE","package.json"],
packages/hd-transport-react-native/package.json (1)

22-23: Use workspace protocol for internal deps.

Reduces release friction across the monorepo.

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.10",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
     "@onekeyfe/react-native-ble-utils": "^0.1.4",
packages/hd-transport-electron/package.json (4)

23-26: Declare Linux HCI socket as optional peer.

Improves DX on Linux without forcing installs elsewhere.

Add under peerDependencies (not a diff to the changed block):
{
"@stoprocent/bluetooth-hci-socket": ">=2.2.0"
}
and:
{
"peerDependenciesMeta": {
"@stoprocent/bluetooth-hci-socket": { "optional": true }
}
}


16-21: Ensure native rebuild for Electron.

@noble/HCI may need ABI rebuilds for Electron >=25.

#!/bin/bash
# Check for electron-rebuild integration
rg -nP 'electron-rebuild|prebuild' -C2 packages/hd-transport-electron || true
echo 'Suggestion: add postinstall "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket"'

28-33: Loosen noble; pick up patch fixes.

Allow patched releases from @stoprocent/noble.

   "dependencies": {
     "@onekeyfe/hd-core": "1.1.10-alpha.10",
     "@onekeyfe/hd-shared": "1.1.10-alpha.10",
     "@onekeyfe/hd-transport": "1.1.10-alpha.10",
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
     "p-retry": "^4.6.2"
   },

28-33: Prefer workspaces for internal deps.

Keeps versions in lockstep without manual bumps.

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10-alpha.10",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.10",
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
     "@stoprocent/noble": "^2.3.4",
     "p-retry": "^4.6.2"
   },
packages/hd-ble-sdk/package.json (2)

3-3: Add publish hygiene: files + sideEffects

Smaller tarballs and better tree‑shaking.

   "version": "1.1.10-alpha.10",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],

23-25: Switch internal deps to workspace protocol

Reduces churn across the monorepo.

-    "@onekeyfe/hd-core": "1.1.10-alpha.10",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport-react-native": "1.1.10-alpha.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"

Run to confirm pins are actually published before release:

#!/bin/bash
set -euo pipefail
for p in \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]"
do
  printf "%-55s" "$p"; npm view "$p" version >/dev/null && echo "published" || echo "NOT PUBLISHED"
done
packages/hd-transport-web-device/package.json (2)

3-3: Release hygiene: add files/sideEffects and update CHANGELOG/tag

Keep artifacts slim and releases auditable.

   "version": "1.1.10-alpha.10",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],

Confirm a CHANGELOG entry and annotated tag exist for 1.1.10-alpha.10.


23-24: Use workspace protocol for internal deps

Prevents repeated manual bumps.

-    "@onekeyfe/hd-shared": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport": "1.1.10-alpha.10"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
-    "@onekeyfe/hd-transport-electron": "1.1.10-alpha.10",
+    "@onekeyfe/hd-transport-electron": "workspace:*",

Check that these versions are on npm if you keep exact pins:

#!/bin/bash
set -euo pipefail
for p in \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]"
do
  printf "%-55s" "$p"; npm view "$p" version >/dev/null && echo "published" || echo "NOT PUBLISHED"
done

Also applies to: 27-27

packages/hd-common-connect-sdk/package.json (2)

3-3: Add files/sideEffects; document/tag the release

Improves package size and traceability.

   "version": "1.1.10-alpha.10",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],

Ensure there’s a CHANGELOG entry and tag for 1.1.10-alpha.10.


23-28: Prefer workspace: for internal deps*

Keeps workspace packages in sync.

-    "@onekeyfe/hd-core": "1.1.10-alpha.10",
-    "@onekeyfe/hd-shared": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport-emulator": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport-http": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10-alpha.10",
-    "@onekeyfe/hd-transport-web-device": "1.1.10-alpha.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-emulator": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"

Sanity-check pins if not switching to workspace:

#!/bin/bash
set -euo pipefail
for p in \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]" \
  "@onekeyfe/[email protected]"
do
  printf "%-60s" "$p"; npm view "$p" version >/dev/null && echo "published" || echo "NOT PUBLISHED"
done
packages/connect-examples/expo-example/package.json (1)

10-10: Use a single cross-env

Simpler and clearer.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
packages/connect-examples/electron-example/package.json (2)

18-20: Add :ci variants that never publish

Avoid accidental publishes from CI.

   "scripts": {
@@
     "rebuild:deps": "electron-builder install-app-deps",
     "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
     "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",

25-26: Ensure HCI socket is installed; drop node-fetch on Electron 28+

Noble needs the HCI native binding on Linux. Electron 28/Node 18 has fetch built-in.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10-alpha.10",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",
     "debug": "4.3.4",
@@
-    "node-fetch": "^2.6.7"
+    // remove: Electron 28/Node 18 includes global fetch

Double-check bundling keeps these native modules external and unpacked at runtime (webpack externals + electron-builder asarUnpack).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9ad546e and 290b6d2.

📒 Files selected for processing (15)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: release-web
🔇 Additional comments (9)
packages/shared/package.json (1)

3-3: Version bump aligned. LGTM.
Consistent with the monorepo updates.

packages/hd-web-sdk/package.json (1)

24-27: Internal deps verified – all published

I ran the npm view check and confirmed that each internal package at version 1.1.10-alpha.10 is available:

  • @onekeyfe/hd-core ⇒ 1.1.10-alpha.10
  • @onekeyfe/hd-shared ⇒ 1.1.10-alpha.10
  • @onekeyfe/hd-transport-http ⇒ 1.1.10-alpha.10
  • @onekeyfe/hd-transport-web-device ⇒ 1.1.10-alpha.10

No broken installs expected.

packages/connect-examples/expo-playground/package.json (2)

3-3: Version bump OK.
Private example; no publish impact.


20-22: Deps exist on npm
Verified that versions 1.1.10-alpha.10 of

  • @onekeyfe/hd-core
  • @onekeyfe/hd-shared
  • @onekeyfe/hd-web-sdk
    are all published on npm. Lines 20–22 in packages/connect-examples/expo-playground/package.json are good to go.
packages/hd-transport/package.json (1)

4-4: Nice description cleanup.
Clear and user-facing.

packages/hd-transport-emulator/package.json (2)

3-3: Version bump aligned.
Looks consistent with the repo.


27-28: Internal deps published and ready
Both @onekeyfe/[email protected] and @onekeyfe/[email protected] are live on npm. No install breakage expected.

packages/core/package.json (1)

28-31: Upgrade axios to 1.x
axios 0.27.2 has known vulnerabilities. Bump to the latest 1.x release for security and consistency.

In packages/core/package.json:

-   "axios": "^0.27.2",
+   "axios": "^1.11.0",

After updating, run your install at the repo root (npm install or yarn install) to refresh the lockfile. Then:

  • Run tests to catch any breaking changes.
  • Re-run npm audit to confirm the vulnerabilities are resolved.
packages/connect-examples/expo-example/package.json (1)

22-25: Example pins verified as published
All four @onekeyfe packages at version 1.1.10-alpha.10 are available on npm. Examples will install cleanly outside the monorepo.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
packages/hd-transport-electron/src/ble-ops.ts (4)

1-2: Fix type import; keep typings local and consistent

@stoprocent/noble may not ship TS types. Also you mix type sources. Import both Characteristic and Logger from the local wrapper.

-import type { Characteristic } from '@stoprocent/noble';
-import type { Logger } from './types/noble-extended';
+import type { Characteristic, Logger } from './types/noble-extended';

Run to confirm local typings exist and to find remaining imports from @stoprocent/noble:

#!/bin/bash
fd noble-extended
rg -nP "export\s+type\s+Characteristic\b" -- types/noble-extended.* || true
rg -n "@stoprocent/noble" -g '!**/dist/**'

56-72: Harden data path: structured logs + protect app callback

Avoid crashing the pipeline if the app callback throws. Use compact, structured logs.

   notifyCharacteristic.removeAllListeners('data');
   notifyCharacteristic.on('data', (data: Buffer) => {
@@
-    const result = processNotificationData(deviceId, data);
-    if (result.error) {
-      logger?.error('[BLE-OPS] Packet processing error:', result.error);
-      return;
-    }
-    if (result.isComplete && result.completePacket) {
-      const appCb = notificationCallbacks.get(deviceId);
-      if (appCb) appCb(result.completePacket);
-    }
+    const result = processNotificationData(deviceId, data);
+    if (result.error) {
+      logger?.error?.('[BLE-OPS] Packet processing error', { deviceId, error: result.error });
+      return;
+    }
+    if (result.isComplete && result.completePacket) {
+      const appCb = notificationCallbacks.get(deviceId);
+      if (appCb) {
+        try { appCb(result.completePacket); }
+        catch (e) { logger?.error?.('[BLE-OPS] App callback failed', { deviceId, error: String(e) }); }
+      }
+    }

46-54: Subscribe: loosen error type and normalize rejection

The callback may pass string/Error/unknown. Use unknown and keep rejection.

-  await new Promise<void>((resolve, reject) => {
-    notifyCharacteristic.subscribe((error?: Error) => {
+  await new Promise<void>((resolve, reject) => {
+    notifyCharacteristic.subscribe((error?: unknown) => {
       if (error) {
         reject(error);
         return;
       }
       resolve();
     });
   });

42-45: Unsubscribe: capture error and don’t fail hard

Unsubscribe often returns spurious errors; log and continue to resubscribe.

-  await new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe(() => resolve());
-  });
+  await new Promise<void>((resolve) => {
+    notifyCharacteristic.unsubscribe((error?: unknown) => {
+      if (error) logger?.warn?.('[BLE-OPS] Unsubscribe before resubscribe failed', { deviceId, error: String(error) });
+      resolve();
+    });
+  });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 290b6d2 and 046e95c.

📒 Files selected for processing (1)
  • packages/hd-transport-electron/src/ble-ops.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (22)
  • GitHub Check: build (22)
🔇 Additional comments (1)
packages/hd-transport-electron/src/ble-ops.ts (1)

34-36: Keep throw: upstream handles error
softRefreshSubscription throws on missing notifyCharacteristic so the caller’s try/catch in noble-ble-handler logs the failure and retries—no UI crash.

Likely an incorrect or invalid review comment.

@huhuanming huhuanming enabled auto-merge (squash) August 28, 2025 02:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/connect-examples/electron-example/package.json (1)

24-33: Drop node-fetch v2; use global fetch (Electron 28/Node 18+)

This trims deps and native rebuilds.

   "dependencies": {
@@
-    "node-fetch": "^2.6.7"
+    // use global fetch or undici if needed
   },

Follow-up: replace imports of node-fetch with global fetch.

packages/hd-transport-electron/src/noble-ble-handler.ts (3)

810-815: Enumerate scan callback: widen error type

Treat callback error as unknown and normalize message.

-  noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+  noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: unknown) => {
     if (error) {
       clearTimeout(timeout);
-      logger?.error('[NobleBLE] Failed to start scanning:', error);
-      reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+      logger?.error('[NobleBLE] Failed to start scanning:', error);
+      reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, (error as any)?.message ?? String(error)));
       return;
     }

1083-1090: Fresh connection: normalize callback error

Same normalization as above.

-      freshPeripheral.connect((error: Error | undefined) => {
+      freshPeripheral.connect((error: unknown) => {
         if (error) {
-          reject(new Error(`Fresh peripheral connection failed: ${error.message}`));
+          reject(new Error(`Fresh peripheral connection failed: ${(error as any)?.message ?? String(error)}`));

1291-1303: Connect timeout path: treat error as unknown

Avoid .message access on non-Error.

-    connectedPeripheral.connect(async (error: Error | undefined) => {
+    connectedPeripheral.connect(async (error: unknown) => {
@@
-      if (error) {
+      if (error) {
         logger?.error('[NobleBLE] Connection failed:', error);
-        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, error.message));
+        reject(ERRORS.TypedError(HardwareErrorCode.BleConnectedError, (error as any)?.message ?? String(error)));
         return;
       }
♻️ Duplicate comments (39)
packages/shared/package.json (1)

3-3: Add sideEffects and a files whitelist to tighten publishes.
Keeps installs lean and enables tree-shaking.

Apply:

 {
   "name": "@onekeyfe/hd-shared",
   "version": "1.1.10",
   "description": "Hardware SDK's shared tool library",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport/package.json (1)

3-4: Publish hygiene: add sideEffects and files.
Align with other packages for smaller bundles.

Apply:

 {
   "name": "@onekeyfe/hd-transport",
   "version": "1.1.10",
   "description": "Transport layer abstractions and utilities for OneKey hardware SDK.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-lowlevel/package.json (2)

22-24: Internal deps aligned; consider workspace protocol.
Eases future version bumps in the monorepo.

Apply:

   "dependencies": {
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10"
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*"
   }

3-3: Add sideEffects and files.
Consistent publish hygiene with other libs.

Apply:

   "version": "1.1.10",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-transport-electron/package.json (4)

23-26: Declare Linux HCI socket as an optional peer.
Guides Linux users without forcing installs on macOS/Windows.

Apply:

   "peerDependencies": {
     "electron": ">=25.0.0",
-    "electron-log": ">=4.0.0"
+    "electron-log": ">=4.0.0",
+    "@stoprocent/bluetooth-hci-socket": ">=2.2.0"
   },
+  "peerDependenciesMeta": {
+    "@stoprocent/bluetooth-hci-socket": {
+      "optional": true
+    }
+  },

16-21: Electron native addon rebuild hook.
Prevent ABI mismatches at runtime.

Apply:

   "scripts": {
     "dev": "rimraf dist && rollup -c ../../build/rollup.config.js -w",
     "build": "rimraf dist && rollup -c ../../build/rollup.config.js",
-    "lint": "eslint .",
-    "lint:fix": "eslint . --fix"
+    "lint": "eslint .",
+    "lint:fix": "eslint . --fix",
+    "postinstall": "electron-rebuild -f -w @stoprocent/noble -w @stoprocent/bluetooth-hci-socket || true"
   },

And add the devDependency:

   "devDependencies": {
+    "electron-rebuild": "^3.2.10",
     "@types/web-bluetooth": "^0.0.17",
     "electron": "^25.0.0",
     "typescript": "^5.3.3"
   }

28-33: Loosen noble to pick up patch fixes.
Allow automatic patch updates from the noble stack.

Apply:

   "dependencies": {
     "@onekeyfe/hd-core": "1.1.10",
     "@onekeyfe/hd-shared": "1.1.10",
     "@onekeyfe/hd-transport": "1.1.10",
-    "@stoprocent/noble": "2.3.4",
+    "@stoprocent/noble": "^2.3.4",
     "p-retry": "^4.6.2"
   },

28-33: Optional: switch internal deps to workspace protocol.
Reduces release friction in the monorepo.

Apply:

   "dependencies": {
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10",
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
     "@stoprocent/noble": "^2.3.4",
     "p-retry": "^4.6.2"
   },
packages/core/package.json (1)

3-5: Add sideEffects and files.
Smaller, tree‑shakable publishes.

Apply:

   "version": "1.1.10",
   "description": "Core processes and APIs for communicating with OneKey hardware devices.",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/connect-examples/electron-example/package.json (3)

5-8: Fix misleading package description

This is a hardware example, not “End-to-end encrypted workspaces”.

   "productName": "HardwareExample",
   "executableName": "onekey-hardware-example",
   "version": "1.1.10",
   "author": "OneKey",
-  "description": "End-to-end encrypted workspaces for teams",
+  "description": "OneKey hardware Electron example app",

25-27: Ensure HCI socket is installed for Linux

Noble needs the HCI native binding. Add it explicitly to avoid runtime “module not found”.

   "dependencies": {
     "@onekeyfe/hd-transport-electron": "1.1.10",
     "@stoprocent/noble": "2.3.4",
+    "@stoprocent/bluetooth-hci-socket": "^2.2.3",

18-20: Add CI-safe build scripts (don’t publish from CI by default)

Provide :ci variants to prevent accidental publishes in pipelines.

   "scripts": {
     "rebuild:deps": "electron-builder install-app-deps",
-    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
-    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish always",
+    "make:win": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish always",
+    "make:mac:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --mac --config electron-builder.config.js --publish never",
+    "make:win:ci": "yarn rebuild:deps && yarn clean:build && yarn build-electron-web && electron-builder build --win --config electron-builder.config.js --publish never",
packages/hd-transport-electron/src/noble-ble-handler.ts (15)

713-769: Targeted scan: don’t remove a listener you haven’t added; normalize scan errors

Remove the redundant removeListener and handle error as unknown.

@@
-    // Remove any existing discover listeners to prevent memory leaks
-    if (noble) {
-      noble.removeListener('discover', onDiscover);
-      noble.on('discover', onDiscover);
-    }
+    if (noble) {
+      noble.on('discover', onDiscover);
+    }
@@
-    // Start scanning
+    // Start scanning
     if (noble) {
-      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: Error) => {
+      noble.startScanning(ONEKEY_SERVICE_UUIDS, false, (error?: unknown) => {
         if (error) {
           clearTimeout(timeout);
           if (noble) {
             noble.removeListener('discover', onDiscover);
           }
-          logger?.error('[NobleBLE] Failed to start targeted scan:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, error.message));
+          logger?.error('[NobleBLE] Failed to start targeted scan:', error);
+          reject(ERRORS.TypedError(HardwareErrorCode.BleScanError, (error as any)?.message ?? String(error)));
           return;
         }

21-21: Type imports from @stoprocent/noble may be missing

Import types from 'noble' or add a d.ts shim re-exporting types to avoid build breaks.

-import type { Peripheral, Service, Characteristic } from '@stoprocent/noble';
+import type { Peripheral, Service, Characteristic } from 'noble';

If you prefer sticking to @stoprocent, declare a local module and re-export types.


83-83: Make connection timeout configurable (default 3s)

Let ops tune without rebuilds; 3s can be tight vs pairing probes.

-const CONNECTION_TIMEOUT = 3000; // 3 seconds for device connection
+const CONNECTION_TIMEOUT = Number(process.env.ONEKEY_BLE_CONNECT_TIMEOUT_MS ?? 3000);

106-111: Reduce notification log noise

Log notifications at debug to cut log volume and overhead.

-  logger?.info('[NobleBLE] Notification', {
+  logger?.debug('[NobleBLE] Notification', {

863-877: Simplify listener teardown—clear all if supported

Use removeAllListeners() if available; keep try/catch.

-  try {
-    (noble as any).removeAllListeners('discover');
-    (noble as any).removeAllListeners('stateChange');
+  try {
+    const ee = noble as unknown as { removeAllListeners?: (event?: string) => void };
+    ee.removeAllListeners?.();
     logger?.info('[NobleBLE] All Noble listeners cleaned up');

1024-1031: Force reconnect: avoid .message on possibly non-Error

Normalize the error string.

-        reject(new Error(`Force reconnect failed: ${error.message}`));
+        reject(new Error(`Force reconnect failed: ${(error as any)?.message ?? String(error)}`));

1410-1414: Drop hard-coded subscribe context fields

Remove always-false fields to avoid confusion.

   logger?.info('[NobleBLE] Subscribe context', {
     deviceId,
     opStateBefore: subscriptionOperations.get(deviceId) || 'idle',
-    paired: false,
-    hasController: false,
   });

1471-1483: Subscribe/unsubscribe callbacks: accept unknown and normalize

Prevent crashes on string errors.

-    await new Promise<void>(resolve => {
-      notifyCharacteristic.unsubscribe(() => {
-        resolve();
-      });
-    });
-    await new Promise<void>((resolve, reject) => {
-      notifyCharacteristic.subscribe((error?: Error) => {
-        if (error) {
-          reject(error);
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.unsubscribe((error?: unknown) => {
+        if (error) return reject(new Error((error as any)?.message ?? String(error)));
+        resolve();
+      });
+    });
+    await new Promise<void>((resolve, reject) => {
+      notifyCharacteristic.subscribe((error?: unknown) => {
+        if (error) {
+          reject(new Error((error as any)?.message ?? String(error)));
           return;
         }
         resolve();
       });
     });

1514-1533: Remove stray console logs or gate behind DEBUG

Use logger or env-gated console to keep production logs clean.

-  console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
+  if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] Attempting to set up Noble BLE handlers.');
@@
-    console.log('[NobleBLE] NOBLE_VERSION_771');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] NOBLE bootstrap');
@@
-    console.log('[NobleBLE] electron-log loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron-log loaded successfully.');
@@
-    console.log('[NobleBLE] electron.ipcMain loaded successfully.');
+    if (process.env.ONEKEY_BLE_DEBUG) console.log('[NobleBLE] electron.ipcMain loaded successfully.');
@@
-    console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);
+    if (process.env.ONEKEY_BLE_DEBUG) console.log(`[NobleBLE] Registering handler for: ${EOneKeyBleMessageKeys.NOBLE_BLE_ENUMERATE}`);

1581-1583: Log byte length, not hex chars

hexData.length is nibbles. Report bytes.

-        logger?.info('[NobleBLE] IPC WRITE', { deviceId, len: hexData.length });
+        logger?.info('[NobleBLE] IPC WRITE', { deviceId, bytes: Math.floor(hexData.length / 2) });

27-27: Typo in comment

“bundlinpissues” → “bundling issues”.

-// Noble will be dynamically imported to avoid bundlinpissues
+// Noble will be dynamically imported to avoid bundling issues

1354-1389: Unsubscribe: error is unknown; always reset state (you already do—good)

Widen type and keep the cleanup you have.

-  return new Promise<void>(resolve => {
-    notifyCharacteristic.unsubscribe((error: Error | undefined) => {
+  return new Promise<void>(resolve => {
+    notifyCharacteristic.unsubscribe((error: unknown) => {
       if (error) {
         logger?.error('[NobleBLE] Notification unsubscription failed:', error);

477-492: Write callback: treat error as unknown and normalize

Avoid assuming Error and keep logs consistent.

-  return new Promise((resolve, reject) => {
-    writeCharacteristic.write(buffer, true, (error?: Error) => {
+  return new Promise((resolve, reject) => {
+    writeCharacteristic.write(buffer, true, (error?: unknown) => {
       if (error) {
-        logger?.error('[NobleBLE] Write failed', { deviceId, error: String(error) });
-        reject(error);
+        logger?.error('[NobleBLE] Write failed', { deviceId, error });
+        reject(new Error((error as any)?.message ?? String(error)));
         return;
       }
       resolve();
     });
   });

916-990: Service/characteristic discovery: error should be unknown; normalize messages

Prevent crashes on non-Error values.

-      (error: Error | undefined, services: Service[]) => {
+      (error: unknown, services: Service[]) => {
         if (error) {
           logger?.error('[NobleBLE] Service discovery failed:', error);
-          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, error.message));
+          reject(ERRORS.TypedError(HardwareErrorCode.BleServiceNotFound, (error as any)?.message ?? String(error)));
           return;
         }
@@
-          (error: Error | undefined, characteristics: Characteristic[]) => {
+          (error: unknown, characteristics: Characteristic[]) => {
             if (error) {
               logger?.error('[NobleBLE] Characteristic discovery failed:', error);
-              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, error.message));
+              reject(ERRORS.TypedError(HardwareErrorCode.BleCharacteristicNotFound, (error as any)?.message ?? String(error)));
               return;
             }

374-440: cancelOperations option is a no-op—perform best-effort unsubscribe before wiping state

Cancel OS notifications/listeners before deleting maps to avoid leaks and stale OS state.

 function cleanupDevice(
@@
-  // 1. 清理设备状态
+  // 1. 最佳努力取消进行中的通知/操作
+  if (cancelOperations) {
+    try {
+      const ch = deviceCharacteristics.get(deviceId);
+      ch?.notify.removeAllListeners('data');
+      ch?.notify.unsubscribe?.(() => {});
+    } catch (e) {
+      safeLog(logger, 'warn', '[NobleBLE] Best-effort unsubscribe failed during cleanup', e);
+    }
+  }
+
+  // 2. 清理设备状态
@@
-  // 2. 发送断开事件(如果需要)
+  // 3. 发送断开事件(如果需要)
packages/connect-examples/expo-playground/package.json (1)

20-23: Consider workspace protocol for local dev

Keeps the playground in sync with the monorepo during development.

-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-web-sdk": "1.1.10",
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-web-sdk": "workspace:^",
packages/hd-web-sdk/package.json (2)

3-3: Slim the publish and improve tree-shaking

Add files list and sideEffects flag.

   "version": "1.1.10",
+  "sideEffects": false,
+  "files": [
+    "build/**/*",
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],

24-28: Align internal deps via workspace protocol (or ensure all 1.1.10s are live)

This reduces churn and registry coupling.

   "dependencies": {
     "@onekeyfe/cross-inpage-provider-core": "^0.0.17",
-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-http": "1.1.10",
-    "@onekeyfe/hd-transport-web-device": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:^",
+    "@onekeyfe/hd-shared": "workspace:^",
+    "@onekeyfe/hd-transport-http": "workspace:^",
+    "@onekeyfe/hd-transport-web-device": "workspace:^"
   },
packages/hd-transport-emulator/package.json (1)

29-29: Security: upgrade axios to a patched 1.x

0.27.x carries known CSRF/SSRF issues. Move to ≥1.8.2 (prefer latest 1.x). Run tests after bump.

-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
#!/bin/bash
# Verify no 0.x axios remains and audit
rg -nP '"axios":\s*"(\^|~)?0\.' packages || echo "✓ No axios 0.x pins"
cd packages/hd-transport-emulator
npm install --package-lock-only --legacy-peer-deps
npm audit --omit=dev || true
packages/hd-transport-http/package.json (1)

29-29: Security: upgrade axios to a patched 1.x

Please bump axios; 0.27.x is vulnerable. Prefer latest 1.x and re-run builds.

-    "axios": "^0.27.2",
+    "axios": "^1.11.0",
#!/bin/bash
cd packages/hd-transport-http
npm install --package-lock-only --legacy-peer-deps
npm ls axios --depth=0 || true
npm audit --omit=dev | grep -i -C3 axios || echo "✓ No axios advisories found"
packages/hd-transport-react-native/package.json (1)

22-23: Optional: use workspace protocol for internal deps

This eases future coordinated bumps across the monorepo.

-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport": "1.1.10",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport": "workspace:*",
packages/hd-ble-sdk/package.json (2)

23-25: Optional: switch internal deps to workspace protocol

Reduces churn during releases.

-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-react-native": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-react-native": "workspace:*"

3-3: Publish hygiene: add files and sideEffects

Slimmer tarballs and better tree‑shaking.

   "version": "1.1.10",
+  "sideEffects": false,
+  "files": [
+    "dist/**/*",
+    "README.md",
+    "LICENSE",
+    "package.json"
+  ],
packages/hd-common-connect-sdk/package.json (2)

3-3: Release hygiene: ensure CHANGELOG entry and Git tag for 1.1.10

Add notes and push the tag so downstreams can track changes.

#!/bin/bash
fd -HI 'CHANGELOG.md' -t f | xargs -I{} sh -c 'echo "-- {} --"; rg -n "^##\\s*\\[?1\\.1\\.10" "{}" || true'
git tag -l | rg '^v?1\.1\.10$' || echo "Missing git tag 1.1.10"

23-28: Optional: adopt workspace protocol for internal deps

Makes synchronized releases easier.

-    "@onekeyfe/hd-core": "1.1.10",
-    "@onekeyfe/hd-shared": "1.1.10",
-    "@onekeyfe/hd-transport-emulator": "1.1.10",
-    "@onekeyfe/hd-transport-http": "1.1.10",
-    "@onekeyfe/hd-transport-lowlevel": "1.1.10",
-    "@onekeyfe/hd-transport-web-device": "1.1.10"
+    "@onekeyfe/hd-core": "workspace:*",
+    "@onekeyfe/hd-shared": "workspace:*",
+    "@onekeyfe/hd-transport-emulator": "workspace:*",
+    "@onekeyfe/hd-transport-http": "workspace:*",
+    "@onekeyfe/hd-transport-lowlevel": "workspace:*",
+    "@onekeyfe/hd-transport-web-device": "workspace:*"
packages/connect-examples/expo-example/package.json (2)

22-25: Verify pinned @onekeyfe packages are published at 1.1.10.

Pins are good if they exist on npm. Please confirm to avoid broken installs outside the monorepo.

#!/bin/bash
set -euo pipefail
pkgs=(
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
  "@onekeyfe/[email protected]"
)
for p in "${pkgs[@]}"; do
  printf "%-45s" "$p"
  if npm view "$p" version >/dev/null 2>&1; then
    echo "published: $(npm view "$p" version)"
  else
    echo "NOT PUBLISHED"
  fi
done

echo -e "\nDist-tags:"
for n in "@onekeyfe/hd-ble-sdk" "@onekeyfe/hd-common-connect-sdk" "@onekeyfe/hd-core" "@onekeyfe/hd-web-sdk"; do
  printf "%-35s" "$n"; npm view "$n" dist-tags --json 2>/dev/null || true
done

10-10: Use a single cross-env.

One cross-env is enough; drop the duplicate for clarity.

-    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 cross-env NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
+    "build:electron-web": "cross-env NODE_OPTIONS=--max-old-space-size=4096 NODE_ENV=production EXPO_ELECTRON_MODE=true yarn expo export:web && node scripts/moveWebBuild.js",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge Base: Disabled due to data retention organization setting

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 046e95c and 8e3b513.

📒 Files selected for processing (16)
  • packages/connect-examples/electron-example/package.json (2 hunks)
  • packages/connect-examples/expo-example/package.json (2 hunks)
  • packages/connect-examples/expo-playground/package.json (2 hunks)
  • packages/core/package.json (2 hunks)
  • packages/hd-ble-sdk/package.json (2 hunks)
  • packages/hd-common-connect-sdk/package.json (2 hunks)
  • packages/hd-transport-electron/package.json (2 hunks)
  • packages/hd-transport-electron/src/noble-ble-handler.ts (29 hunks)
  • packages/hd-transport-emulator/package.json (2 hunks)
  • packages/hd-transport-http/package.json (2 hunks)
  • packages/hd-transport-lowlevel/package.json (2 hunks)
  • packages/hd-transport-react-native/package.json (2 hunks)
  • packages/hd-transport-web-device/package.json (2 hunks)
  • packages/hd-transport/package.json (1 hunks)
  • packages/hd-web-sdk/package.json (2 hunks)
  • packages/shared/package.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build (22)
  • GitHub Check: lint (22)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (14)
packages/shared/package.json (1)

3-3: Version sync looks good.
No functional concerns with the bump to 1.1.10.

packages/hd-transport/package.json (1)

3-4: Nice cleanup: version and description.
Good step toward a stable release artifact.

packages/hd-transport-lowlevel/package.json (1)

3-3: Version bump OK.
Matches the repo-wide 1.1.10 sync.

packages/hd-transport-electron/package.json (1)

3-3: Release version OK.

packages/core/package.json (1)

3-5: Good: stable version and real description.

packages/hd-transport-electron/src/noble-ble-handler.ts (1)

223-268: Robust poweredOff handling—nice work

Thorough state reset on Bluetooth off. This should kill many “ghost” states. LGTM.

packages/hd-transport-emulator/package.json (2)

3-3: Version bump to 1.1.10 looks good

Aligned with the rest of the stack.


27-28: Internal deps aligned to 1.1.10

Consistent pins. Good.

packages/hd-transport-http/package.json (2)

3-3: Version bump to 1.1.10 looks good

Matches monorepo alignment.


27-28: Internal deps aligned to 1.1.10

Solid.

packages/hd-transport-react-native/package.json (1)

3-3: Version bump to 1.1.10 looks good

Release alignment is consistent.

packages/hd-ble-sdk/package.json (1)

3-3: Version bump to 1.1.10 looks good

No issues spotted.

packages/hd-common-connect-sdk/package.json (1)

3-3: Version bump to 1.1.10 looks good

Aligned with dependent packages.

packages/connect-examples/expo-example/package.json (1)

3-3: Version bump looks fine (example is private).

No action needed.

@wabicai wabicai force-pushed the fix/custom-binding-ble branch from 8e3b513 to 8d0b628 Compare August 28, 2025 10:42
@huhuanming huhuanming merged commit 33aa9a5 into onekey Aug 28, 2025
6 of 8 checks passed
@huhuanming huhuanming deleted the fix/custom-binding-ble branch August 28, 2025 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants