New:Socket for Asana Is Now Available.Learn more
Get Started

@stoprocent/noble

Package Overview
Dependencies
Maintainers
1
Versions
94
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.7.1
to
2.8.0
+12
lib/pairing-kinds.js
// Integer values mirror WinRT's DevicePairingKinds enum (Windows.Devices.Enumeration).
// Only the Windows binding uses these; other bindings surface a deterministic
// "Pairing is not supported on this platform" error when pairing is requested.
module.exports = {
None: 0,
ConfirmOnly: 0x00000008,
DisplayPin: 0x00000010,
ConfirmPinMatch: 0x00000020,
ProvidePin: 0x00000040,
ProvidePassword: 0x00000080,
ConfirmPassword: 0x00000100,
};
// Integer values mirror WinRT's DevicePairingProtectionLevel enum
// (Windows.Devices.Enumeration). Only the Windows binding uses these;
// other bindings' `pair()` surfaces "Pairing is not supported on this
// platform".
module.exports = {
Default: 0,
None: 1,
Encryption: 2,
EncryptionAndAuthentication: 3,
};
const UINT32_MAX = 0xFFFFFFFF;
module.exports = function isUint32 (value) {
return Number.isInteger(value) && value >= 0 && value <= UINT32_MAX;
};
+49
-16

@@ -28,2 +28,32 @@ /// <reference types="node" />

/**
* Mirrors WinRT's `DevicePairingKinds` (Windows.Devices.Enumeration).
* Bitfield: pass a single kind or OR several together to advertise
* which ceremonies the caller knows how to handle.
*
* Only the Windows binding honors this; `pair()` on hci-socket / dbus /
* mac surfaces "Pairing is not supported on this platform" regardless
* of the value.
*/
export enum DevicePairingKinds {
None = 0,
ConfirmOnly = 0x00000008,
DisplayPin = 0x00000010,
ConfirmPinMatch = 0x00000020,
ProvidePin = 0x00000040,
ProvidePassword = 0x00000080,
ConfirmPassword = 0x00000100,
}
/**
* Mirrors WinRT's `DevicePairingProtectionLevel`.
* Used only by the Windows binding.
*/
export enum DevicePairingProtectionLevel {
Default = 0,
None = 1,
Encryption = 2,
EncryptionAndAuthentication = 3,
}
export interface ConnectOptions {

@@ -49,8 +79,3 @@ addressType?: PeripheralAddressType;

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>;
pairAsync(idOrAddress: PeripheralIdOrAddress, kind?: DevicePairingKinds, protectionLevel?: DevicePairingProtectionLevel): Promise<void>;

@@ -60,8 +85,2 @@ startScanning(serviceUUIDs?: string[], allowDuplicates?: boolean, callback?: (error?: Error) => 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;

@@ -71,2 +90,16 @@ reset(): void;

setAddress(address: string): void;
/**
* Pair with a peripheral. `kind` defaults to
* `DevicePairingKinds.ConfirmOnly` and `protectionLevel` defaults to
* `DevicePairingProtectionLevel.Encryption`. On Windows, `ConfirmOnly`,
* `DisplayPin`, and `ConfirmPinMatch` use the OS pairing dialog. PIN /
* password kinds (`ProvidePin`, `ProvidePassword`, `ConfirmPassword`) are
* rejected because this library does not collect secrets to pass into
* WinRT. On non-Windows platforms the call surfaces a "Pairing is not
* supported" error.
*/
pair(idOrAddress: PeripheralIdOrAddress, callback: (error: Error | undefined) => void): void;
pair(idOrAddress: PeripheralIdOrAddress, kind?: DevicePairingKinds, callback?: (error: Error | undefined) => void): void;
pair(idOrAddress: PeripheralIdOrAddress, kind: DevicePairingKinds, protectionLevel: DevicePairingProtectionLevel, callback?: (error: Error | undefined) => void): void;

@@ -126,4 +159,3 @@ on(event: "stateChange", listener: (state: AdapterState) => void): this;

connectAsync(): Promise<void>;
/** Windows only; ConfirmOnly ("Just Works") ceremony only. */
pairAsync(): Promise<void>;
pairAsync(kind?: DevicePairingKinds, protectionLevel?: DevicePairingProtectionLevel): Promise<void>;
disconnectAsync(): Promise<void>;

@@ -139,4 +171,5 @@ updateRssiAsync(): Promise<number>;

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

@@ -143,0 +176,0 @@ updateRssi(callback?: (error: Error | undefined, rssi: number) => void): void;

const withBindings = require('./lib/resolve-bindings');
const hciStatusMessage = require('./lib/hci-status-message');
const DevicePairingKinds = require('./lib/pairing-kinds');
const DevicePairingProtectionLevel = require('./lib/pairing-protection-level');

@@ -7,1 +9,3 @@ module.exports = withBindings();

module.exports.hciStatusMessage = hciStatusMessage;
module.exports.DevicePairingKinds = DevicePairingKinds;
module.exports.DevicePairingProtectionLevel = DevicePairingProtectionLevel;

@@ -9,2 +9,5 @@ const debug = require('debug')('noble');

const Descriptor = require('./descriptor');
const DevicePairingKinds = require('./pairing-kinds');
const DevicePairingProtectionLevel = require('./pairing-protection-level');
const isUint32 = require('./uint32');

@@ -437,4 +440,58 @@ class Noble extends NobleEventEmitter {

pair (idOrAddress, callback) {
pair (idOrAddress, kind, protectionLevel, callback) {
const identifier = this._getPeripheralId(idOrAddress);
// Backward compat: `pair(id, callback)` — the legacy signature passes
// the callback in the 2nd position. Detect that and default `kind` to
// ConfirmOnly so existing callers see no behavior change. The new
// signature's `kind` is a number, so a function in this slot is
// unambiguously the legacy callback.
if (typeof kind === 'function') {
callback = kind;
kind = undefined;
protectionLevel = undefined;
} else if (typeof protectionLevel === 'function') {
callback = protectionLevel;
protectionLevel = undefined;
}
if (kind !== undefined && kind !== null && !isUint32(kind)) {
const err = new Error('pair() kind must be a finite uint32 DevicePairingKinds bitmask');
if (typeof callback === 'function') {
callback(err);
} else {
this.emit('warning', err.message);
}
return;
}
// Reject an empty mask up-front. Otherwise the native handler's
// `kind & kinds` would always be 0, causing PairAsync to fail with
// a generic RejectedByHandler that's indistinguishable from the
// device refusing a ceremony we asked for.
if (kind === 0) {
const err = new Error('pair() requires at least one DevicePairingKinds value; DevicePairingKinds.None matches no ceremony');
if (typeof callback === 'function') {
callback(err);
} else {
this.emit('warning', err.message);
}
return;
}
const pairingKind = (kind === undefined || kind === null)
? DevicePairingKinds.ConfirmOnly
: kind;
if (protectionLevel !== undefined &&
protectionLevel !== null &&
!isUint32(protectionLevel)) {
const err = new Error('pair() protectionLevel must be a finite uint32 DevicePairingProtectionLevel');
if (typeof callback === 'function') {
callback(err);
} else {
this.emit('warning', err.message);
}
return;
}
const pairingProtectionLevel = (protectionLevel === undefined || protectionLevel === null)
? DevicePairingProtectionLevel.Encryption
: protectionLevel;
if (typeof callback === 'function') {

@@ -447,3 +504,4 @@ this.onceExclusive(`pair:${identifier}`, error => callback(error));

// callers don't hang waiting for a 'pair' completion that will
// never come.
// never come. The requested `kind` is ignored on unsupported
// platforms.
const err = new Error('Pairing is not supported on this platform');

@@ -461,8 +519,8 @@ const peripheral = this._peripherals.get(identifier);

}
this._bindings.pair(identifier);
this._bindings.pair(identifier, pairingKind, pairingProtectionLevel);
}
async pairAsync (idOrAddress) {
async pairAsync (idOrAddress, kind, protectionLevel) {
return new Promise((resolve, reject) => {
this.pair(idOrAddress, error => error ? reject(error) : resolve());
this.pair(idOrAddress, kind, protectionLevel, error => error ? reject(error) : resolve());
});

@@ -469,0 +527,0 @@ }

@@ -60,15 +60,28 @@ 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);
pair (kind, protectionLevel, callback) {
// Backward compat: `peripheral.pair(callback)` — the legacy signature
// passes the callback in the 1st position. Detect that and forward
// `undefined` as the kind so `noble.pair` defaults to ConfirmOnly.
//
// The callback is delegated 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.
if (typeof kind === 'function') {
callback = kind;
kind = undefined;
protectionLevel = undefined;
} else if (typeof protectionLevel === 'function') {
callback = protectionLevel;
protectionLevel = undefined;
}
this._noble.pair(this.id, kind, protectionLevel, callback);
}
async pairAsync () {
async pairAsync (kind, protectionLevel) {
return new Promise((resolve, reject) => {
this.pair(error => error ? reject(error) : resolve());
this.pair(kind, protectionLevel, error => error ? reject(error) : resolve());
});

@@ -75,0 +88,0 @@ }

@@ -391,6 +391,6 @@ #include "ble_manager.h"

bool BLEManager::Pair(const std::string& uuid)
bool BLEManager::Pair(const std::string& uuid,
winrt::Windows::Devices::Enumeration::DevicePairingKinds kinds,
winrt::Windows::Devices::Enumeration::DevicePairingProtectionLevel protectionLevel)
{
using winrt::Windows::Devices::Enumeration::DevicePairingKinds;
auto it = mDeviceMap.find(uuid);

@@ -439,28 +439,55 @@ if (it == mDeviceMap.end() || !it->second.device.has_value())

// 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.
// These ceremony values are not supported because this library does
// not collect PIN/password input to forward into an Accept(...) overload.
constexpr uint32_t unsupportedKinds =
0x00000040u | 0x00000080u | 0x00000100u;
if ((static_cast<uint32_t>(kinds) & unsupportedKinds) != 0)
{
mEmit.Paired(uuid, false,
"pairing kinds requiring a PIN or password are not supported");
return true;
}
// Always go through `pairing.Custom()`: only DeviceInformationCustomPairing
// accepts a `DevicePairingKinds` mask (DeviceInformationPairing.PairAsync
// takes only a protection level). The caller-supplied `kinds` may
// include ConfirmOnly, DisplayPin, or ConfirmPinMatch; PIN/password
// ceremonies are rejected before we get here because this library does
// not provide a way to collect or forward secrets to Accept(...).
//
// 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.
// The PairingRequested lambda must Accept() exactly once for any kind
// the caller advertised in `kinds`. Some devices negotiated through
// ConfirmPinMatch can still raise a ConfirmOnly prompt; treat that as a
// compatible fallback so pairing does not fail with status=Failed.
// For other kinds outside the mask we return without Accept(), which
// Windows treats as a rejection and PairAsync completes with
// RejectedByHandler / AuthenticationNotAllowed. We never forward a PIN
// or password back to JS — Windows drives the UI for non-ConfirmOnly
// ceremonies.
custom = pairing.Custom();
token = custom.PairingRequested(
[](winrt::Windows::Devices::Enumeration::DeviceInformationCustomPairing const&,
winrt::Windows::Devices::Enumeration::DevicePairingRequestedEventArgs const& args) {
[kinds](winrt::Windows::Devices::Enumeration::DeviceInformationCustomPairing const&,
winrt::Windows::Devices::Enumeration::DevicePairingRequestedEventArgs const& args) {
auto kind = args.PairingKind();
auto kindMask = static_cast<uint32_t>(kind);
auto requestedKinds = static_cast<uint32_t>(kinds);
bool confirmPinMatchRequested =
(requestedKinds &
static_cast<uint32_t>(
winrt::Windows::Devices::Enumeration::DevicePairingKinds::ConfirmPinMatch)) != 0;
bool confirmOnlyFallback =
confirmPinMatchRequested &&
kind == winrt::Windows::Devices::Enumeration::DevicePairingKinds::ConfirmOnly;
LOGE("pairing requested, kind=%d", static_cast<int>(kind));
if (kind == DevicePairingKinds::ConfirmOnly)
if ((kindMask & requestedKinds) != 0 || confirmOnlyFallback)
{
args.Accept();
}
// Any other kind: do not call Accept() — Windows treats the
// handler returning as a rejection.
// Any kind outside the caller's mask: 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)
custom.PairAsync(kinds, protectionLevel)
.Completed(completed);

@@ -467,0 +494,0 @@ }

@@ -25,3 +25,6 @@ #pragma once

bool Connect(const std::string& uuid);
bool Pair(const std::string& uuid);
bool Pair(
const std::string& uuid,
winrt::Windows::Devices::Enumeration::DevicePairingKinds kinds,
winrt::Windows::Devices::Enumeration::DevicePairingProtectionLevel protectionLevel);
bool Disconnect(const std::string& uuid);

@@ -28,0 +31,0 @@ bool CancelConnect(const std::string& uuid);

@@ -71,1 +71,24 @@ #include "napi_winrt.h"

}
uint32_t getUint32(const Napi::Value& value, uint32_t def)
{
if (!value.IsNumber())
{
return def;
}
const double asDouble = value.As<Napi::Number>().DoubleValue();
// Reject NaN / infinities / negatives / values outside uint32 range.
// (NaN makes both comparisons false, so it is rejected here.)
if (!(asDouble >= 0.0 && asDouble <= 4294967295.0))
{
return def;
}
const uint32_t asUint32 = static_cast<uint32_t>(asDouble);
// Reject fractional values (and values not exactly representable as uint32).
if (asDouble != static_cast<double>(asUint32))
{
return def;
}
return asUint32;
}

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

bool getBool(const Napi::Value& value, bool def);
uint32_t getUint32(const Napi::Value& value, uint32_t def);

@@ -11,0 +12,0 @@ winrt::guid napiToUuid(Napi::String string);

@@ -104,3 +104,3 @@ #include "noble_winrt.h"

// pair(deviceUuid)
// pair(deviceUuid, kinds?, protectionLevel?)
Napi::Value NobleWinrt::Pair(const Napi::CallbackInfo& info)

@@ -111,3 +111,18 @@ {

auto uuid = info[0].As<Napi::String>().Utf8Value();
manager->Pair(uuid);
// `kinds` is a DevicePairingKinds bitmask. The JS layer (noble.js) is
// expected to normalize the value before forwarding here — including
// rejecting DevicePairingKinds.None — so any default we pick is a
// belt-and-suspenders backstop, not a primary code path. We default
// to None (0) rather than ConfirmOnly to surface unexpected callers
// via the lambda's `(kind & kinds) == 0` check (PairAsync will fail
// with RejectedByHandler), instead of silently falling through to a
// ceremony the caller never asked for.
using winrt::Windows::Devices::Enumeration::DevicePairingKinds;
using winrt::Windows::Devices::Enumeration::DevicePairingProtectionLevel;
auto kinds = static_cast<DevicePairingKinds>(
getUint32(info[1], static_cast<uint32_t>(DevicePairingKinds::None)));
auto protectionLevel = static_cast<DevicePairingProtectionLevel>(
getUint32(info[2], static_cast<uint32_t>(DevicePairingProtectionLevel::Encryption)));
manager->Pair(uuid, kinds, protectionLevel);
return info.Env().Undefined();

@@ -114,0 +129,0 @@ }

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

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

@@ -12,0 +12,0 @@ "type": "git",

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

```typescript
import noble, {
DevicePairingKinds,
DevicePairingProtectionLevel,
} from '@stoprocent/noble';
// Pair at the Noble level (by ID or address) or on a Peripheral instance.

@@ -400,2 +405,9 @@ await noble.pairAsync(idOrAddress);

// Optionally choose pairing ceremony + protection level (Windows only).
await noble.pairAsync(
idOrAddress,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.EncryptionAndAuthentication
);
// Callback form is also available on both.

@@ -420,9 +432,9 @@ noble.pair(idOrAddress, error => { /* ... */ });

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.
- **Windows pairing options.** `kind` defaults to `ConfirmOnly` and
`protectionLevel` defaults to `Encryption`. You can pass a
`DevicePairingKinds` bitmask (single or OR-ed values) and (optionally)
`DevicePairingProtectionLevel`.
`ConfirmOnly`, `DisplayPin`, and `ConfirmPinMatch` use Windows' native UI;
PIN/password kinds (`ProvidePin`, `ProvidePassword`, `ConfirmPassword`) are
rejected because this library does not collect secrets to pass into WinRT.

@@ -429,0 +441,0 @@ ### Service Methods

@@ -11,2 +11,11 @@ jest.mock('../lib/resolve-bindings', () => jest.fn(() => ({})));

});
test('exposes DevicePairingProtectionLevel constants', () => {
expect(noble.DevicePairingProtectionLevel).toEqual({
Default: 0,
None: 1,
Encryption: 2,
EncryptionAndAuthentication: 3,
});
});
});

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

expect(mockNoble.pair).toHaveBeenCalledWith(mockId, undefined);
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, undefined, undefined, undefined);
expect(mockNoble.pair).toHaveBeenCalledTimes(1);

@@ -194,3 +194,5 @@ });

expect(mockNoble.pair).toHaveBeenCalledWith(mockId, callback);
// `kind`/`protectionLevel` are forwarded as undefined
// (1st-arg-was-callback swap), and callback is in the 4th position.
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, undefined, undefined, callback);
expect(mockNoble.pair).toHaveBeenCalledTimes(1);

@@ -200,3 +202,3 @@

// event, so drive the callback it received rather than the peripheral.
mockNoble.pair.mock.calls[0][1](null);
mockNoble.pair.mock.calls[0][3](null);
expect(callback).toHaveBeenCalledWith(null);

@@ -211,3 +213,3 @@ expect(callback).toHaveBeenCalledTimes(1);

peripheral.pair(callback);
mockNoble.pair.mock.calls[0][1](error);
mockNoble.pair.mock.calls[0][3](error);

@@ -230,4 +232,25 @@ expect(callback).toHaveBeenCalledWith(error);

expect(mockNoble.pair).toHaveBeenCalledWith(mockId, callback);
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, undefined, undefined, callback);
});
test('should forward explicit kind alongside callback', () => {
const kind = 0x00000010; // DevicePairingKinds.DisplayPin
const callback = jest.fn();
peripheral.pair(kind, callback);
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, kind, undefined, callback);
expect(mockNoble.pair).toHaveBeenCalledTimes(1);
});
test('should forward explicit protection level alongside kind + callback', () => {
const kind = 0x00000008; // ConfirmOnly
const protectionLevel = 3; // EncryptionAndAuthentication
const callback = jest.fn();
peripheral.pair(kind, protectionLevel, callback);
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, kind, protectionLevel, callback);
expect(mockNoble.pair).toHaveBeenCalledTimes(1);
});
});

@@ -239,6 +262,7 @@

// Noble releases the delegated callback via pair:${id}; simulate it.
mockNoble.pair.mock.calls[0][1](null);
// After kind-swap, pairAsync calls noble.pair(id, undefined, undefined, callback).
mockNoble.pair.mock.calls[0][3](null);
await expect(promise).resolves.toBeUndefined();
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, expect.any(Function));
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, undefined, undefined, expect.any(Function));
expect(mockNoble.pair).toHaveBeenCalledTimes(1);

@@ -249,6 +273,27 @@ });

const promise = peripheral.pairAsync();
mockNoble.pair.mock.calls[0][1](new Error('pairing failed'));
mockNoble.pair.mock.calls[0][3](new Error('pairing failed'));
await expect(promise).rejects.toThrow('pairing failed');
});
test('should forward explicit kind', async () => {
const kind = 0x00000010; // DisplayPin
const promise = peripheral.pairAsync(kind);
mockNoble.pair.mock.calls[0][3](null);
await expect(promise).resolves.toBeUndefined();
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, kind, undefined, expect.any(Function));
expect(mockNoble.pair).toHaveBeenCalledTimes(1);
});
test('should forward explicit protection level', async () => {
const kind = 0x00000010; // DisplayPin
const protectionLevel = 1; // None
const promise = peripheral.pairAsync(kind, protectionLevel);
mockNoble.pair.mock.calls[0][3](null);
await expect(promise).resolves.toBeUndefined();
expect(mockNoble.pair).toHaveBeenCalledWith(mockId, kind, protectionLevel, expect.any(Function));
expect(mockNoble.pair).toHaveBeenCalledTimes(1);
});
});

@@ -255,0 +300,0 @@

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

describe('pair', () => {
// Mirror lib/pairing-kinds.js — kept in sync manually since these tests
// exercise the JS layer (no WinRT binding in scope).
const DevicePairingKinds = {
None: 0,
ConfirmOnly: 0x00000008,
DisplayPin: 0x00000010,
ProvidePin: 0x00000040,
};
const DevicePairingProtectionLevel = {
None: 1,
Encryption: 2,
EncryptionAndAuthentication: 3,
};
// Use a hex id so `_getPeripheralId` accepts it without calling the
// mocked addressToId (whose return value would otherwise become the
// forwarded identifier).
const peripheralUuidHex = '00112233445566778899aabbccddeeff';
test('should default to ConfirmOnly when kind is omitted', () => {
mockBindings.pair = jest.fn();
noble.pair(peripheralUuidHex, () => {});
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.Encryption
);
});
test('should forward explicit kind to the binding', () => {
mockBindings.pair = jest.fn();
noble.pair(peripheralUuidHex, DevicePairingKinds.DisplayPin, () => {});
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.DisplayPin,
DevicePairingProtectionLevel.Encryption
);
});
test('should accept a bitmask kind (multiple OR-ed values)', () => {
mockBindings.pair = jest.fn();
const mask = DevicePairingKinds.ConfirmOnly | DevicePairingKinds.ProvidePin;
noble.pair(peripheralUuidHex, mask, () => {});
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
mask,
DevicePairingProtectionLevel.Encryption
);
});
test('should forward explicit protection level to the binding', () => {
mockBindings.pair = jest.fn();
noble.pair(
peripheralUuidHex,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.EncryptionAndAuthentication,
() => {}
);
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.EncryptionAndAuthentication
);
});
test('should treat function in 2nd position as callback (legacy signature)', () => {
mockBindings.pair = jest.fn();
const cb = jest.fn();
noble.pair(peripheralUuidHex, cb);
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.Encryption
);
});
test('should treat function in 3rd position as callback (legacy signature)', () => {
mockBindings.pair = jest.fn();
const cb = jest.fn();
noble.pair(peripheralUuidHex, DevicePairingKinds.ConfirmOnly, cb);
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.Encryption
);
});
test('pairAsync should default to ConfirmOnly', async () => {
mockBindings.pair = jest.fn();
const promise = noble.pairAsync(peripheralUuidHex);
// pairAsync awaits a `pair:${identifier}` event; emit success
// (null error) to resolve the promise.
noble.emit(`pair:${peripheralUuidHex}`, null);
await promise;
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.Encryption
);
});
test('pairAsync should forward explicit kind', async () => {
mockBindings.pair = jest.fn();
const promise = noble.pairAsync(peripheralUuidHex, DevicePairingKinds.ProvidePin);
noble.emit(`pair:${peripheralUuidHex}`, null);
await promise;
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.ProvidePin,
DevicePairingProtectionLevel.Encryption
);
});
test('pairAsync should forward explicit protection level', async () => {
mockBindings.pair = jest.fn();
const promise = noble.pairAsync(
peripheralUuidHex,
DevicePairingKinds.ProvidePin,
DevicePairingProtectionLevel.None
);
noble.emit(`pair:${peripheralUuidHex}`, null);
await promise;
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuidHex,
DevicePairingKinds.ProvidePin,
DevicePairingProtectionLevel.None
);
});
test('should reject invalid kind values', () => {
mockBindings.pair = jest.fn();
const cb = jest.fn();
noble.pair(peripheralUuidHex, Number.NaN, cb);
expect(cb).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledWith(expect.any(Error));
expect(cb.mock.calls[0][0].message).toBe(
'pair() kind must be a finite uint32 DevicePairingKinds bitmask'
);
expect(mockBindings.pair).not.toHaveBeenCalled();
});
test('should reject DevicePairingKinds.None (empty mask)', () => {
mockBindings.pair = jest.fn();
const cb = jest.fn();
noble.pair(peripheralUuidHex, DevicePairingKinds.None, cb);
expect(cb).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledWith(expect.any(Error));
expect(cb.mock.calls[0][0].message).toBe(
'pair() requires at least one DevicePairingKinds value; DevicePairingKinds.None matches no ceremony'
);
expect(mockBindings.pair).not.toHaveBeenCalled();
});
test('should reject invalid protection level type', () => {
mockBindings.pair = jest.fn();
const cb = jest.fn();
noble.pair(peripheralUuidHex, DevicePairingKinds.ConfirmOnly, 1.5, cb);
expect(cb).toHaveBeenCalledTimes(1);
expect(cb).toHaveBeenCalledWith(expect.any(Error));
expect(cb.mock.calls[0][0].message).toBe(
'pair() protectionLevel must be a finite uint32 DevicePairingProtectionLevel'
);
expect(mockBindings.pair).not.toHaveBeenCalled();
});
test('should surface deterministic error when binding has no pair method', () => {
mockBindings.pair = jest.fn();
delete mockBindings.pair;
const cb = jest.fn();
noble.pair(peripheralUuidHex, DevicePairingKinds.DisplayPin, cb);
expect(cb).toHaveBeenCalledTimes(1);
const err = cb.mock.calls[0][0];
expect(err).toBeInstanceOf(Error);
expect(err.message).toBe('Pairing is not supported on this platform');
});
});
describe('onDisconnect', () => {

@@ -671,3 +852,13 @@ test('should emit disconnect on existing peripheral', () => {

describe('pair', () => {
test('should delegate to binding', () => {
// Mirror lib/pairing-kinds.js — used by these legacy tests for assertions
// when the new pair() defaults to ConfirmOnly.
const DevicePairingKinds = {
None: 0,
ConfirmOnly: 0x00000008,
};
const DevicePairingProtectionLevel = {
Encryption: 2,
};
test('should delegate to binding with ConfirmOnly default', () => {
const peripheralUuid = 'aabbccddeeff';

@@ -678,3 +869,7 @@

expect(mockBindings.pair).toHaveBeenCalledWith(peripheralUuid);
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuid,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.Encryption
);
expect(mockBindings.pair).toHaveBeenCalledTimes(1);

@@ -711,2 +906,9 @@ });

describe('pairAsync', () => {
const DevicePairingKinds = {
ConfirmOnly: 0x00000008,
};
const DevicePairingProtectionLevel = {
Encryption: 2,
};
test('should resolve on success', async () => {

@@ -721,3 +923,7 @@ const peripheralUuid = 'aabbccddeeff';

await expect(promise).resolves.toBeUndefined();
expect(mockBindings.pair).toHaveBeenCalledWith(peripheralUuid);
expect(mockBindings.pair).toHaveBeenCalledWith(
peripheralUuid,
DevicePairingKinds.ConfirmOnly,
DevicePairingProtectionLevel.Encryption
);
});

@@ -724,0 +930,0 @@

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