UNPKG

@rwesa/payu-ble

Version:

A flexible, smart Bluetooth Low Energy challenge system for secure device connections

393 lines (293 loc) 11.8 kB
# payu-ble 🔐 A flexible, smart Bluetooth Low Energy (BLE) challenge system that allows developers to secure BLE connections using customizable challenges like math problems, riddles, hash puzzles, and custom validators. [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support%20Development-orange?style=for-the-badge&logo=buy-me-a-coffee&logoColor=white)](https://www.buymeacoffee.com/rwesa) ## Features - 🔐 **Secure BLE connections** with dynamic challenges - 🧮 **Built-in challenge types**: arithmetic, hash-based, and custom - ⚡ **Lightweight and modular** design - 🔧 **Fully configurable** and extendable - 📱 **Framework-agnostic** - works with any JavaScript/TypeScript project - ⏰ **Time-based availability** controls - 🎯 **Helper utilities** for common use cases ## Installation ```bash npm install @rwesa/payu-ble ``` ## Quick Start ### ES Modules (ESM) - Recommended ```typescript import { createChallenge, verifyAnswer, setBLEAvailability, helpers } from '@rwesa/payu-ble'; // or import PayuBLE, { createChallenge, helpers } from '@rwesa/payu-ble'; // Create a math challenge const challenge = createChallenge({ type: 'arithmetic', difficulty: 2 }); console.log(challenge.prompt); // "Solve: 15 + 7 × 3" // Verify an answer const isCorrect = verifyAnswer('36'); // true // Set device availability (only available during business hours) setBLEAvailability(helpers.timeBased([9, 10, 11, 12, 13, 14, 15, 16, 17])); ``` ### CommonJS (CJS) - Legacy Support ```javascript const { createChallenge, verifyAnswer, setBLEAvailability, helpers } = require('@rwesa/payu-ble'); // or const PayuBLE = require('@rwesa/payu-ble').default; // Same usage as above... ``` ## Challenge Types ### Arithmetic Challenges Create math problems with varying difficulty levels: ```typescript // Easy: simple addition/subtraction createChallenge({ type: 'arithmetic', difficulty: 1 // 5 + 3 }); // Medium: includes multiplication createChallenge({ type: 'arithmetic', difficulty: 2 // 12 × 4 }); // Hard: order of operations createChallenge({ type: 'arithmetic', difficulty: 3 // 25 + 6 × 8 }); // Expert: complex expressions createChallenge({ type: 'arithmetic', difficulty: 4 // (75 + 15) × 12 - 8 }); ``` ### Hash-Based Challenges Create cryptographic puzzles based on device ID and timestamp: ```typescript createChallenge({ type: 'hash' }); // Generates: "What is (SHA256("device123456781640995200")) % 100?" ``` ### Custom Challenges Create riddles, word puzzles, or any custom validation: ```typescript // Riddle with multiple valid answers createChallenge({ type: 'custom', formula: () => "What has keys but can't open locks?", validAnswers: ['piano', 'a piano', 'keyboard'], caseInsensitive: true }); // Custom validation logic createChallenge({ type: 'custom', formula: () => "Enter the current year:", validate: (input) => parseInt(input) === new Date().getFullYear() }); // Time-sensitive challenge createChallenge({ type: 'custom', formula: () => { const code = Math.floor(Date.now() / 60000) % 1000; // Changes every minute return `Enter code: ${code.toString().padStart(3, '0')}`; }, validate: (input) => { const expectedCode = Math.floor(Date.now() / 60000) % 1000; return input === expectedCode.toString().padStart(3, '0'); }, ttl: 60000 // 1 minute expiry }); ``` ## Availability Controls Control when your BLE device advertises itself: ```typescript import { setBLEAvailability, helpers } from 'payu-ble'; // Only available during specific hours setBLEAvailability(helpers.timeBased([9, 10, 11, 14, 15, 16])); // Available during scheduled time slots setBLEAvailability(helpers.scheduleBasedAvailability([ { start: '09:00', end: '12:00' }, { start: '14:00', end: '17:00' } ])); // Always available setBLEAvailability(helpers.alwaysAvailable()); // Custom availability logic setBLEAvailability(() => { const now = new Date(); return now.getDay() >= 1 && now.getDay() <= 5; // Weekdays only }); ``` ## Device Identity The `getHardwareDeviceId()` helper provides a consistent, unique device identifier that persists across application restarts. This is particularly useful for incorporating device-specific data into challenges and ensuring consistent behavior. ### ESM Usage ```typescript import { getHardwareDeviceId } from '@rwesa/payu-ble'; // Get consistent device ID const deviceId = getHardwareDeviceId(); console.log(deviceId); // "a1b2c3d4e5f6a7b8" // Use in arithmetic formulas const challenge = createChallenge({ type: 'custom', formula: () => { const id = getHardwareDeviceId(); const numericId = parseInt(id.substring(0, 8), 16) % 1000; return `Calculate: ${numericId} + 42`; }, validate: (input) => { const id = getHardwareDeviceId(); const numericId = parseInt(id.substring(0, 8), 16) % 1000; return parseInt(input) === numericId + 42; } }); ``` ### CommonJS Usage ```javascript const { getHardwareDeviceId } = require('@rwesa/payu-ble'); // Get consistent device ID const deviceId = getHardwareDeviceId(); // Strip characters for formulas const numericId = deviceId.replace(/[^0-9]/g, '').substring(0, 6); console.log(numericId); // "123456" (numbers only) ``` ### Platform Compatibility The helper automatically detects hardware IDs using platform-specific methods: - **Linux**: Uses `/etc/machine-id` or `/var/lib/dbus/machine-id` - **Raspberry Pi**: Falls back to CPU serial from `/proc/cpuinfo` - **macOS**: Uses `system_profiler` to get Hardware UUID - **Windows**: Uses `wmic csproduct get uuid` ### Fallback Strategy If no hardware ID is available, the function: 1. Generates a UUID and stores it in `~/.payu-ble-id` 2. Reuses the stored UUID on subsequent calls 3. Ensures consistency across application restarts This makes the device ID reliable across different platforms and environments. ## Advanced Examples ### Company Device Verification ```typescript import PayuBLE from '@rwesa/payu-ble'; const companyBLE = new PayuBLE('COMPANY_DEVICE_001'); // Create a challenge that incorporates company-specific data companyBLE.createChallenge({ type: 'custom', formula: () => { const timestamp = Date.now(); const deviceId = companyBLE.getDeviceId(); return `Verify device access: ${deviceId.substring(0, 8)}-${timestamp}`; }, validate: (input) => { // Custom verification logic for company devices return validateCompanyAccess(input); }, ttl: 300000 // 5 minutes }); function validateCompanyAccess(input: string): boolean { // Implement your company's verification logic return input.includes('VERIFIED'); } ``` ### Gaming Use Case ```typescript // Kids' game where solving riddles unlocks devices const gameChallenge = createChallenge({ type: 'custom', formula: () => { const riddles = [ "I'm tall when I'm young, and short when I'm old. What am I?", "What gets wet while drying?", "What has one eye but can't see?" ]; return riddles[Math.floor(Math.random() * riddles.length)]; }, validAnswers: ['candle', 'towel', 'needle'], caseInsensitive: true }); ``` ### IoT Device with Staff Access ```typescript // Device that only unlocks for known staff during work hours setBLEAvailability(() => { const now = new Date(); const isWorkHours = now.getHours() >= 8 && now.getHours() < 18; const isWeekday = now.getDay() >= 1 && now.getDay() <= 5; return isWorkHours && isWeekday; }); createChallenge({ type: 'custom', formula: () => "Enter your staff ID:", validate: (input) => { const validStaffIds = ['EMP001', 'EMP002', 'EMP003']; return validStaffIds.includes(input.toUpperCase()); } }); ``` ## API Reference ### Core Functions #### `createChallenge(options: ChallengeOptions): Challenge` Creates a new challenge with the specified options. **Options:** - `type`: `'arithmetic' | 'hash' | 'custom'` - `difficulty?`: `1 | 2 | 3 | 4` (for arithmetic challenges) - `formula?`: `() => string` (for custom challenges) - `validate?`: `(input: string) => boolean` (for custom validation) - `validAnswers?`: `string[]` (alternative to validate) - `caseInsensitive?`: `boolean` - `ttl?`: `number` (time-to-live in milliseconds) #### `verifyAnswer(input: string): boolean` Verifies if the provided answer is correct for the current challenge. #### `setBLEAvailability(triggerFn: () => boolean): void` Sets the availability trigger function that determines when the BLE device should be discoverable. ### Helper Functions #### `helpers.timeBased(hours: number[]): () => boolean` Returns a trigger function that makes the device available only during specified hours. #### `helpers.scheduleBasedAvailability(schedule: Array<{start: string, end: string}>): () => boolean` Returns a trigger function based on time slots (e.g., "09:00" to "17:00"). #### `helpers.alwaysAvailable(): () => boolean` Returns a trigger function that always returns true. ### Class-based Usage For multiple device instances: ```typescript import PayuBLE from '@rwesa/payu-ble'; const device1 = new PayuBLE('DEVICE_001'); const device2 = new PayuBLE('DEVICE_002'); // Each device can have its own challenges and availability device1.createChallenge({ type: 'arithmetic', difficulty: 1 }); device2.createChallenge({ type: 'hash' }); ``` ## Platform-Specific Implementations The package includes both placeholder helpers and real platform-specific implementations: ### Basic Helpers (Included in main package) - `helpers.gpioButton(pin)` - Placeholder for GPIO-based availability - `helpers.macOnNetwork(mac)` - Placeholder for network-based detection - `helpers.gpsLocation(zone)` - Placeholder for location-based availability ### Real Platform Implementations For actual hardware integration, import the platform-specific modules: ```typescript // ES Modules import { gpioButton, macOnNetwork, gpsLocationTrigger } from '@rwesa/payu-ble/platform'; // CommonJS const { gpioButton, macOnNetwork, gpsLocationTrigger } = require('@rwesa/payu-ble/platform'); // Set up GPIO button availability (Raspberry Pi, etc.) setBLEAvailability(gpioButton(18, { activeLow: true })); // Set up network MAC detection setBLEAvailability(macOnNetwork('aa:bb:cc:dd:ee:ff')); // Set up GPS geofencing setBLEAvailability(gpsLocationTrigger({ latitude: 40.7128, longitude: -74.0060, radius: 100 // meters })); ``` **Platform Requirements:** - **GPIO**: Requires `onoff` package (Linux/Raspberry Pi) - **Network**: Uses ARP table scanning (Windows/Mac/Linux) - **GPS**: Requires GPS hardware/service integration ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## License MIT Copyright (c) 2025 payu-ble contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.