🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@stoprocent/noble

Package Overview
Dependencies
Maintainers
1
Versions
93
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@stoprocent/noble - npm Package Compare versions

Comparing version
2.5.10
to
2.6.0
+23
-3
index.d.ts

@@ -48,6 +48,18 @@ /// <reference types="node" />

connectAsync(idOrAddress: PeripheralIdOrAddress, options?: ConnectOptions): Promise<Peripheral>;
/**
* Pair with a peripheral. Windows only; requires an already-connected
* peripheral and supports only the ConfirmOnly ("Just Works") ceremony.
* Rejects with 'Pairing is not supported on this platform' elsewhere.
*/
pairAsync(idOrAddress: PeripheralIdOrAddress): Promise<void>;
startScanning(serviceUUIDs?: string[], allowDuplicates?: boolean, callback?: (error?: Error) => void): void;
stopScanning(callback?: () => void): void;
connect(idOrAddress: PeripheralIdOrAddress, options?: ConnectOptions, callback?: (error: Error | undefined, peripheral: Peripheral) => void): void;
/**
* Pair with a peripheral. Windows only; requires an already-connected
* peripheral and supports only the ConfirmOnly ("Just Works") ceremony.
* Calls back with 'Pairing is not supported on this platform' elsewhere.
*/
pair(idOrAddress: PeripheralIdOrAddress, callback?: (error: Error | null) => void): void;
cancelConnect(idOrAddress: PeripheralIdOrAddress, options?: object): void;

@@ -62,4 +74,5 @@ reset(): void;

on(event: "discover", listener: (peripheral: Peripheral) => void): this;
on(event: "pair", listener: (peripheral: Peripheral, error: Error | null) => void): this;
on(event: string, listener: Function): this;
once(event: "stateChange", listener: (state: AdapterState) => void): this;

@@ -69,2 +82,3 @@ once(event: "scanStart", listener: () => void): this;

once(event: "discover", listener: (peripheral: Peripheral) => void): this;
once(event: "pair", listener: (peripheral: Peripheral, error: Error | null) => void): this;
once(event: string, listener: Function): this;

@@ -111,2 +125,4 @@

connectAsync(): Promise<void>;
/** Windows only; ConfirmOnly ("Just Works") ceremony only. */
pairAsync(): Promise<void>;
disconnectAsync(): Promise<void>;

@@ -122,2 +138,4 @@ updateRssiAsync(): Promise<number>;

connect(callback?: (error: Error | undefined) => void): void;
/** Windows only; ConfirmOnly ("Just Works") ceremony only. */
pair(callback?: (error: Error | null) => void): void;
disconnect(callback?: () => void): void;

@@ -136,2 +154,3 @@ updateRssi(callback?: (error: Error | undefined, rssi: number) => void): void;

on(event: "connect", listener: (error: Error | undefined) => void): this;
on(event: "pair", listener: (error: Error | null) => void): this;
on(event: "disconnect", listener: (reason: DisconnectReason) => void): this;

@@ -142,4 +161,5 @@ on(event: "rssiUpdate", listener: (rssi: number) => void): this;

on(event: string, listener: Function): this;
once(event: "connect", listener: (error: Error | undefined) => void): this;
once(event: "pair", listener: (error: Error | null) => void): this;
once(event: "disconnect", listener: (reason: DisconnectReason) => void): this;

@@ -146,0 +166,0 @@ once(event: "rssiUpdate", listener: (rssi: number) => void): this;

@@ -20,2 +20,3 @@ #pragma once

void Connected(const std::string& uuid, const std::string& error = "");
void Paired(const std::string& uuid, bool paired, const std::string& error = "");
void Disconnected(const std::string& uuid);

@@ -22,0 +23,0 @@ void MTU(const std::string& uuid, int mtu);

@@ -157,2 +157,10 @@ #include "Emit.h"

void Emit::Paired(const std::string& uuid, bool paired, const std::string& error)
{
mCallback->call([uuid, paired, error](Napi::Env env, std::vector<napi_value>& args) {
// emit('pair', deviceUuid, paired, error)
args = { _s("pair"), _u(uuid), _b(paired), error.empty() ? env.Null() : _e(error) };
});
}
void Emit::Disconnected(const std::string& uuid)

@@ -159,0 +167,0 @@ {

@@ -65,2 +65,3 @@ const debug = require('debug')('noble');

this._bindings.on('connect', this._onConnect.bind(this));
this._bindings.on('pair', this._onPair.bind(this));
this._bindings.on('disconnect', this._onDisconnect.bind(this));

@@ -424,2 +425,53 @@ this._bindings.on('rssiUpdate', this._onRssiUpdate.bind(this));

pair (idOrAddress, callback) {
const identifier = this._getPeripheralId(idOrAddress);
if (typeof callback === 'function') {
this.onceExclusive(`pair:${identifier}`, error => callback(error));
}
if (typeof this._bindings.pair !== 'function') {
// Only the Windows bindings implement pairing today. Surface a
// deterministic error via the same event the callback waits on, so
// callers don't hang waiting for a 'pair' completion that will
// never come.
const err = new Error('Pairing is not supported on this platform');
const peripheral = this._peripherals.get(identifier);
if (peripheral) {
// Mirror _onPair: surface the outcome on the public Noble 'pair'
// event (peripheral, error) as well as on the peripheral itself, so a
// noble-level listener is notified of the unsupported-platform failure.
this.emit('pair', peripheral, err);
peripheral.emit('pair', err);
}
this.emit(`pair:${identifier}`, err);
return;
}
this._bindings.pair(identifier);
}
async pairAsync (idOrAddress) {
return new Promise((resolve, reject) => {
this.pair(idOrAddress, error => error ? reject(error) : resolve());
});
}
_onPair (peripheralId, paired, error) {
const peripheral = this._peripherals.get(peripheralId);
const failure = error || (paired ? null : new Error('pairing failed'));
// Always release the completion callback/promise registered as
// pair:${peripheralId} by Noble#pair — even when the peripheral is no
// longer tracked. Otherwise an untracked id leaves the caller hanging.
this.emit(`pair:${peripheralId}`, failure);
if (peripheral) {
// Public Noble-level pair event, mirroring 'discover' (subject first):
// (peripheral, error). Only fires for a known peripheral; the untracked
// case is surfaced via 'warning' below.
this.emit('pair', peripheral, failure);
peripheral.emit('pair', failure);
} else {
this.emit('warning', `unknown peripheral ${peripheralId} pair result received!`);
}
}
cancelConnect (idOrAddress, parameters) {

@@ -426,0 +478,0 @@ // Get the identifier for the peripheral

@@ -60,2 +60,18 @@ const NobleEventEmitter = require('./noble-event-emitter');

pair (callback) {
// Delegate the callback to Noble#pair so it is released via the internal
// pair:${id} event. Noble#_onPair always emits pair:${id} (even when this
// peripheral has been removed from Noble's map during cleanup), whereas it
// only emits this peripheral's 'pair' event while still tracked. Registering
// on the peripheral event would therefore leave the callback hanging if
// pairing completes after the peripheral is gone.
this._noble.pair(this.id, callback);
}
async pairAsync () {
return new Promise((resolve, reject) => {
this.pair(error => error ? reject(error) : resolve());
});
}
cancelConnect (options) {

@@ -62,0 +78,0 @@ if (this.state === 'connecting') {

@@ -9,4 +9,8 @@ #include "ble_manager.h"

#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
#include <winrt/Windows.Devices.Enumeration.h>
using winrt::Windows::Devices::Enumeration::DevicePairingProtectionLevel;
using winrt::Windows::Devices::Enumeration::DevicePairingResult;
using winrt::Windows::Devices::Enumeration::DevicePairingResultStatus;
using winrt::Windows::Devices::Bluetooth::BluetoothCacheMode;

@@ -388,2 +392,185 @@ using winrt::Windows::Devices::Bluetooth::BluetoothConnectionStatus;

bool BLEManager::Pair(const std::string& uuid)
{
using winrt::Windows::Devices::Enumeration::DevicePairingKinds;
auto it = mDeviceMap.find(uuid);
if (it == mDeviceMap.end() || !it->second.device.has_value())
{
mEmit.Paired(uuid, false, "device not connected");
return true;
}
BluetoothLEDevice& device = *it->second.device;
// The complete WinRT pairing sequence runs under one exception boundary.
// Each of the calls below (device.DeviceInformation().Pairing(),
// pairing.IsPaired(), pairing.CanPair(), pairing.Custom(), the handler
// registration, and PairAsync().Completed()) can throw winrt::hresult_error
// on bad device state, revoked instances, or RPC failure to the enumerator
// service. A throw from any of them that escapes Pair() would propagate out
// of the JS binding's QueuedEventRegistration::ForwardByBaton dispatch and
// tear down the node module — that's the crash we're guarding against.
//
// `custom`, `token`, and `handlerRegistered` are declared before the try so
// both catch blocks can see them. `custom` is null-initialised because it
// is reassigned by pairing.Custom() inside the try and must be safe to
// inspect in the catch. `handlerRegistered` gates the revocation so a throw
// before the handler is attached cannot dereference an invalid token.
DeviceInformationCustomPairing custom{nullptr};
winrt::event_token token{};
bool handlerRegistered = false;
try
{
auto pairing = device.DeviceInformation().Pairing();
// Already bonded — report success immediately.
if (pairing.IsPaired())
{
mEmit.Paired(uuid, true);
return true;
}
if (!pairing.CanPair())
{
mEmit.Paired(uuid, false, "device reports it cannot be paired");
return true;
}
// Use custom pairing so we can auto-accept the ConfirmOnly (Just Works)
// ceremony without a UI prompt. The actual kind the device requests is
// logged so we can diagnose failures.
//
// The PairingRequested handler must Accept() the args exactly once for
// ConfirmOnly; for any other kind (DisplayPin / ProvidePassword /
// ConfirmPinMatch etc.) we simply return without Accept(), which Windows
// treats as a rejection and the PairAsync completes with the appropriate
// failure status (RejectedByHandler / AuthenticationNotAllowed). We have
// no UI to surface a PIN or password prompt from this library.
custom = pairing.Custom();
token = custom.PairingRequested(
[](winrt::Windows::Devices::Enumeration::DeviceInformationCustomPairing const&,
winrt::Windows::Devices::Enumeration::DevicePairingRequestedEventArgs const& args) {
auto kind = args.PairingKind();
LOGE("pairing requested, kind=%d", static_cast<int>(kind));
if (kind == DevicePairingKinds::ConfirmOnly)
{
args.Accept();
}
// Any other kind: do not call Accept() — Windows treats the
// handler returning as a rejection.
});
handlerRegistered = true;
auto completed = bind2(this, &BLEManager::OnPaired, uuid, token, custom);
custom.PairAsync(DevicePairingKinds::ConfirmOnly, DevicePairingProtectionLevel::Encryption)
.Completed(completed);
}
catch (const winrt::hresult_error& e)
{
if (handlerRegistered)
{
try { custom.PairingRequested(token); } catch (...) {}
}
mEmit.Paired(uuid, false, "pairing operation failed: " + winrt::to_string(e.message()));
}
catch (const std::exception& e)
{
if (handlerRegistered)
{
try { custom.PairingRequested(token); } catch (...) {}
}
mEmit.Paired(uuid, false, std::string("pairing operation failed: ") + e.what());
}
return true;
}
std::string pairingResultStatusToString(DevicePairingResultStatus status)
{
switch (status)
{
case DevicePairingResultStatus::Paired: return "Paired";
case DevicePairingResultStatus::NotReadyToPair: return "NotReadyToPair";
case DevicePairingResultStatus::NotPaired: return "NotPaired";
case DevicePairingResultStatus::AlreadyPaired: return "AlreadyPaired";
case DevicePairingResultStatus::ConnectionRejected: return "ConnectionRejected";
case DevicePairingResultStatus::TooManyConnections: return "TooManyConnections";
case DevicePairingResultStatus::HardwareFailure: return "HardwareFailure";
case DevicePairingResultStatus::AuthenticationTimeout: return "AuthenticationTimeout";
case DevicePairingResultStatus::AuthenticationNotAllowed: return "AuthenticationNotAllowed";
case DevicePairingResultStatus::AuthenticationFailure: return "AuthenticationFailure";
case DevicePairingResultStatus::NoSupportedProfiles: return "NoSupportedProfiles";
case DevicePairingResultStatus::ProtectionLevelCouldNotBeMet:
return "ProtectionLevelCouldNotBeMet";
case DevicePairingResultStatus::AccessDenied: return "AccessDenied";
case DevicePairingResultStatus::InvalidCeremonyData: return "InvalidCeremonyData";
case DevicePairingResultStatus::PairingCanceled: return "PairingCanceled";
case DevicePairingResultStatus::OperationAlreadyInProgress:
return "OperationAlreadyInProgress";
case DevicePairingResultStatus::RequiredHandlerNotRegistered:
return "RequiredHandlerNotRegistered";
case DevicePairingResultStatus::RejectedByHandler: return "RejectedByHandler";
case DevicePairingResultStatus::RemoteDeviceHasAssociation:
return "RemoteDeviceHasAssociation";
case DevicePairingResultStatus::Failed: return "Failed";
default:
return "Unknown(" + std::to_string(static_cast<int>(status)) + ")";
}
}
void BLEManager::OnPaired(IAsyncOperation<DevicePairingResult> asyncOp, AsyncStatus status,
const std::string uuid,
winrt::event_token token,
winrt::Windows::Devices::Enumeration::DeviceInformationCustomPairing custom)
{
// Revoke the PairingRequested handler now that pairing has settled; without
// this, a stray callback (e.g. if the device re-prompts) would call Accept/
// Reject on already-resolved args and could log confusing messages. Best-effort
// (guarded like the revocation sites in Pair()): a throw here must not skip
// the pairing result emission below. This block deliberately stays outside
// the result-handling try because revocation should always run even when
// the result itself is unusable.
try { custom.PairingRequested(token); } catch (...) {}
// Everything below — status branching, asyncOp.GetResults(), result.Status(),
// logging that touches the status enum, and the final emission — can throw
// winrt::hresult_error (revoked asyncOp, marshalling failures) or a standard
// exception (e.g. formatBluetoothUuid-style misbehaviour surfacing a bad_alloc).
// A throw escaping OnPaired would propagate through the Completed() handler
// that JS bindings registered and terminate the node module. Wrap the whole
// result-handling tail in one try/catch so the consumer always gets a
// deterministic Paired(uuid, false, ...).
try
{
if (status != AsyncStatus::Completed)
{
mEmit.Paired(uuid, false, "pairing operation " + asyncStatusToString(status));
return;
}
auto result = asyncOp.GetResults();
auto resultStatus = result.Status();
LOGE("pairing result status=%s", pairingResultStatusToString(resultStatus).c_str());
if (resultStatus == DevicePairingResultStatus::Paired ||
resultStatus == DevicePairingResultStatus::AlreadyPaired)
{
mEmit.Paired(uuid, true);
}
else
{
mEmit.Paired(uuid, false,
"pairing failed with status " + pairingResultStatusToString(resultStatus));
}
}
catch (const winrt::hresult_error& e)
{
mEmit.Paired(uuid, false,
"pairing result failed: " + winrt::to_string(e.message()));
}
catch (const std::exception& e)
{
mEmit.Paired(uuid, false,
std::string("pairing result failed: ") + e.what());
}
}
bool BLEManager::Disconnect(const std::string& uuid)

@@ -390,0 +577,0 @@ {

@@ -5,2 +5,3 @@ #pragma once

#include <winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h>
#include <winrt/Windows.Devices.Enumeration.h>

@@ -25,2 +26,3 @@ #include "Emit.h"

bool Connect(const std::string& uuid);
bool Pair(const std::string& uuid);
bool Disconnect(const std::string& uuid);

@@ -48,2 +50,3 @@ bool CancelConnect(const std::string& uuid);

void OnConnected(IAsyncOperation<BluetoothLEDevice> asyncOp, AsyncStatus status, std::string uuid);
void OnPaired(IAsyncOperation<winrt::Windows::Devices::Enumeration::DevicePairingResult> asyncOp, AsyncStatus status, std::string uuid, winrt::event_token token, winrt::Windows::Devices::Enumeration::DeviceInformationCustomPairing custom);
void OnConnectionStatusChanged(BluetoothLEDevice device, winrt::Windows::Foundation::IInspectable inspectable);

@@ -50,0 +53,0 @@ void OnGattSessionCreated(IAsyncOperation<GattSession> asyncOp, AsyncStatus status, std::string uuid);

@@ -104,2 +104,12 @@ #include "noble_winrt.h"

// pair(deviceUuid)
Napi::Value NobleWinrt::Pair(const Napi::CallbackInfo& info)
{
CHECK_MANAGER()
ARG1(String)
auto uuid = info[0].As<Napi::String>().Utf8Value();
manager->Pair(uuid);
return info.Env().Undefined();
}
// disconnect(deviceUuid)

@@ -319,2 +329,3 @@ Napi::Value NobleWinrt::Disconnect(const Napi::CallbackInfo& info)

NobleWinrt::InstanceMethod("connect", &NobleWinrt::Connect),
NobleWinrt::InstanceMethod("pair", &NobleWinrt::Pair),
NobleWinrt::InstanceMethod("disconnect", &NobleWinrt::Disconnect),

@@ -321,0 +332,0 @@ NobleWinrt::InstanceMethod("cancelConnect", &NobleWinrt::CancelConnect),

@@ -16,2 +16,3 @@ #pragma once

Napi::Value Connect(const Napi::CallbackInfo&);
Napi::Value Pair(const Napi::CallbackInfo&);
Napi::Value Disconnect(const Napi::CallbackInfo&);

@@ -18,0 +19,0 @@ Napi::Value CancelConnect(const Napi::CallbackInfo&);

+2
-2

@@ -9,3 +9,3 @@ {

"description": "A Node.js BLE (Bluetooth Low Energy) central library.",
"version": "2.5.10",
"version": "2.6.0",
"repository": {

@@ -38,3 +38,3 @@ "type": "git",

"optionalDependencies": {
"@stoprocent/bluetooth-hci-socket": "^2.2.8"
"@stoprocent/bluetooth-hci-socket": "^2.3.0"
},

@@ -41,0 +41,0 @@ "peerDependencies": {

@@ -332,2 +332,5 @@ # ![noble](assets/noble-logo.png)

// Pair with a peripheral by ID or address (Windows only — see "Pairing" below)
await noble.pairAsync(idOrAddress);
// Set adapter address (HCI only on supported devices)

@@ -352,2 +355,5 @@ noble.setAddress('00:11:22:33:44:55');

// Pair with peripheral (Windows only — see "Pairing" below)
await peripheral.pairAsync();
// Update RSSI

@@ -387,2 +393,37 @@ const rssi = await peripheral.updateRssiAsync();

### Pairing
```typescript
// Pair at the Noble level (by ID or address) or on a Peripheral instance.
await noble.pairAsync(idOrAddress);
// or
await peripheral.pairAsync();
// Callback form is also available on both.
noble.pair(idOrAddress, error => { /* ... */ });
peripheral.pair(error => { /* ... */ });
// The pairing outcome is emitted on the peripheral, and (subject, error)
// on Noble, mirroring other events.
peripheral.on('pair', error => { /* error is null on success */ });
noble.on('pair', (peripheral, error) => { /* ... */ });
```
**Platform support and limitations — please read before using pairing:**
- **Windows only.** Pairing is implemented solely by the Windows (WinRT)
binding. On macOS, Linux, and other bindings `pair`/`pairAsync` does not
attempt a native call — it rejects/returns a deterministic
`'Pairing is not supported on this platform'` error so callers do not hang.
- **A connected device is required.** The peripheral must already be
discovered and connected (present in Noble's peripheral map) before pairing
is requested. Pairing an unknown/untracked id fails rather than hanging.
- **Only the `ConfirmOnly` ("Just Works") ceremony is supported.** The
handler auto-accepts `ConfirmOnly` requests without a UI prompt. Any other
ceremony (`DisplayPin`, `ProvidePassword`, `ConfirmPinMatch`, …) is **not**
accepted — this library has no UI to surface a PIN or password — and Windows
completes the operation with the corresponding failure status
(e.g. `RejectedByHandler` / `AuthenticationNotAllowed`), which is reported
back through the `pair` callback/event.
### Service Methods

@@ -389,0 +430,0 @@

@@ -30,2 +30,3 @@ const Peripheral = require('../../lib/peripheral');

connect: jest.fn(),
pair: jest.fn(),
cancelConnect: jest.fn(),

@@ -166,2 +167,71 @@ disconnect: jest.fn(),

describe('pair', () => {
test('should delegate to noble, forwarding the callback', () => {
peripheral.pair();
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, undefined);
expect(mockNoble.pair).toHaveBeenCalledTimes(1);
});
test('should release the callback via Noble#pair (success)', () => {
const callback = jest.fn();
peripheral.pair(callback);
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, callback);
expect(mockNoble.pair).toHaveBeenCalledTimes(1);
// Noble releases the delegated callback through the internal pair:${id}
// event, so drive the callback it received rather than the peripheral.
mockNoble.pair.mock.calls[0][1](null);
expect(callback).toHaveBeenCalledWith(null);
expect(callback).toHaveBeenCalledTimes(1);
});
test('should release the callback via Noble#pair (failure)', () => {
const callback = jest.fn();
const error = new Error('pairing failed');
peripheral.pair(callback);
mockNoble.pair.mock.calls[0][1](error);
expect(callback).toHaveBeenCalledWith(error);
expect(callback).toHaveBeenCalledTimes(1);
});
test('should not hang on the peripheral pair event after removal', () => {
// Regression: the callback used to be registered on the peripheral 'pair'
// event, which Noble only emits while the peripheral is tracked. If
// pairing completes after cleanup (peripheral removed from Noble's map),
// that registration never fired and the callback hung forever. The
// callback is now owned by Noble#pair via pair:${id}.
const callback = jest.fn();
peripheral.pair(callback);
peripheral.emit('pair', new Error('late completion after removal'));
expect(callback).not.toHaveBeenCalled();
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, callback);
});
});
describe('pairAsync', () => {
test('should resolve on success', async () => {
const promise = peripheral.pairAsync();
// Noble releases the delegated callback via pair:${id}; simulate it.
mockNoble.pair.mock.calls[0][1](null);
await expect(promise).resolves.toBeUndefined();
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, expect.any(Function));
expect(mockNoble.pair).toHaveBeenCalledTimes(1);
});
test('should reject on error', async () => {
const promise = peripheral.pairAsync();
mockNoble.pair.mock.calls[0][1](new Error('pairing failed'));
await expect(promise).rejects.toThrow('pairing failed');
});
});
describe('cancelConnect', () => {

@@ -168,0 +238,0 @@ test('not connecting, should resolve', async () => {

@@ -645,2 +645,152 @@ const Noble = require('../lib/noble');

describe('pair', () => {
test('should delegate to binding', () => {
const peripheralUuid = 'aabbccddeeff';
mockBindings.pair = jest.fn();
noble.pair(peripheralUuid);
expect(mockBindings.pair).toHaveBeenCalledWith(peripheralUuid);
expect(mockBindings.pair).toHaveBeenCalledTimes(1);
});
test('should fail synchronously when pairing is unsupported by the bindings', () => {
const peripheralUuid = 'aabbccddeeff';
// mockBindings deliberately has no pair() method (e.g. macOS/Linux).
const callback = jest.fn();
noble.pair(peripheralUuid, callback);
expect(callback).toHaveBeenCalledWith(expect.any(Error));
expect(callback).toHaveBeenCalledTimes(1);
expect(mockBindings.pair).toBeUndefined();
});
test('should emit the public pair event on the unsupported-platform path', () => {
const peripheralUuid = 'aabbccddeeff';
const peripheral = { emit: jest.fn() };
noble._peripherals.set(peripheralUuid, peripheral);
// mockBindings deliberately has no pair() method.
const pairListener = jest.fn();
noble.on('pair', pairListener);
noble.pair(peripheralUuid, () => {});
expect(pairListener).toHaveBeenCalledWith(peripheral, expect.any(Error));
expect(pairListener).toHaveBeenCalledTimes(1);
});
});
describe('pairAsync', () => {
test('should resolve on success', async () => {
const peripheralUuid = 'aabbccddeeff';
mockBindings.pair = jest.fn();
noble._peripherals.set(peripheralUuid, { emit: jest.fn() });
const promise = noble.pairAsync(peripheralUuid);
noble._onPair(peripheralUuid, true);
await expect(promise).resolves.toBeUndefined();
expect(mockBindings.pair).toHaveBeenCalledWith(peripheralUuid);
});
test('should reject on failure', async () => {
const peripheralUuid = 'aabbccddeeff';
mockBindings.pair = jest.fn();
noble._peripherals.set(peripheralUuid, { emit: jest.fn() });
const promise = noble.pairAsync(peripheralUuid);
noble._onPair(peripheralUuid, false, new Error('pairing failed'));
await expect(promise).rejects.toThrow('pairing failed');
});
test('should reject when pairing is unsupported by the bindings', async () => {
const peripheralUuid = 'aabbccddeeff';
// mockBindings deliberately has no pair() method (e.g. macOS/Linux).
const promise = noble.pairAsync(peripheralUuid);
await expect(promise).rejects.toThrow('Pairing is not supported on this platform');
expect(mockBindings.pair).toBeUndefined();
});
});
describe('onPair', () => {
test('should emit pair on existing peripheral (success)', () => {
const emit = jest.fn();
noble._peripherals.set('aabbccddeeff', { emit });
const warningCallback = jest.fn();
noble.on('warning', warningCallback);
noble._onPair('aabbccddeeff', true);
expect(emit).toHaveBeenCalledWith('pair', null);
expect(warningCallback).not.toHaveBeenCalled();
});
test('should emit pair failure on existing peripheral', () => {
const emit = jest.fn();
noble._peripherals.set('aabbccddeeff', { emit });
const error = new Error('pairing failed');
const warningCallback = jest.fn();
noble.on('warning', warningCallback);
noble._onPair('aabbccddeeff', false, error);
expect(emit).toHaveBeenCalledWith('pair', error);
expect(warningCallback).not.toHaveBeenCalled();
});
test('should emit the public Noble pair event on success', () => {
const peripheral = { emit: jest.fn() };
noble._peripherals.set('aabbccddeeff', peripheral);
const pairListener = jest.fn();
noble.on('pair', pairListener);
noble._onPair('aabbccddeeff', true);
expect(pairListener).toHaveBeenCalledWith(peripheral, null);
expect(pairListener).toHaveBeenCalledTimes(1);
});
test('should emit the public Noble pair event on failure', () => {
const peripheral = { emit: jest.fn() };
noble._peripherals.set('aabbccddeeff', peripheral);
const error = new Error('pairing failed');
const pairListener = jest.fn();
noble.on('pair', pairListener);
noble._onPair('aabbccddeeff', false, error);
expect(pairListener).toHaveBeenCalledWith(peripheral, error);
expect(pairListener).toHaveBeenCalledTimes(1);
});
test('should release the completion callback even for an untracked peripheral', () => {
// Regression: Noble#pair registers pair:${id}; if the native side
// completes pairing for an id that is not in _peripherals, the
// completion callback must still fire rather than hang forever.
const peripheralUuid = 'aabbccddeeff';
const warningCallback = jest.fn();
noble.on('warning', warningCallback);
// Ensure Noble#pair takes the supported-bindings path so completion waits
// for _onPair (mirrors native behavior on Windows).
mockBindings.pair = jest.fn();
const completionCallback = jest.fn();
noble.pair(peripheralUuid, completionCallback);
expect(completionCallback).not.toHaveBeenCalled();
noble._onPair(peripheralUuid, false, new Error('device not connected'));
expect(completionCallback).toHaveBeenCalledWith(expect.any(Error));
expect(completionCallback).toHaveBeenCalledTimes(1);
expect(warningCallback).toHaveBeenCalledWith(
'unknown peripheral aabbccddeeff pair result received!'
);
});
});
describe('onDiscover', () => {

@@ -647,0 +797,0 @@ test('should add new peripheral', () => {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet