UNPKG

ttlock-sdk-js

Version:

JavaScript port of the TTLock Android SDK

381 lines (380 loc) 15.5 kB
'use strict'; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NobleWebsocketBinding = void 0; const events_1 = require("events"); const reconnecting_websocket_1 = __importDefault(require("reconnecting-websocket")); const ws_1 = __importDefault(require("ws")); const crypto_js_1 = __importDefault(require("crypto-js")); const chalk_1 = __importDefault(require("chalk")); class NobleWebsocketBinding extends events_1.EventEmitter { constructor(address, port, key, user, pass) { super(); this.aesKey = crypto_js_1.default.enc.Hex.parse(key); this.credentials = user + ':' + pass; this.auth = false; this.wasReady = false; this.buffer = []; this.startScanCommand = null; this.peripherals = new Map(); this.ws = new reconnecting_websocket_1.default(`ws://${address}:${port}/noble`, [], { WebSocket: ws_1.default }); this.on('message', this.onMessage.bind(this)); this.ws.onopen = this.onOpen.bind(this); this.ws.onclose = this.onClose.bind(this); this.ws.onerror = this.onClose.bind(this); this.ws.onmessage = (event) => { try { if (process.env.WEBSOCKET_DEBUG == "1") { console.log("Received: " + chalk_1.default.green(event.data.toString())); } this.emit('message', JSON.parse(event.data.toString())); } catch (error) { console.error(error); } }; } init() { } onOpen() { console.log(chalk_1.default.green('Websocket connected')); } onClose() { this.auth = false; console.log(chalk_1.default.red('Websocket disconnected')); // this.emit('stateChange', 'poweredOff'); for (const [peripheralUuid, peripheral] of this.peripherals) { if (peripheral.connected) { peripheral.connected = false; console.log('Disconnect', peripheralUuid); this.emit('disconnect', peripheralUuid); } if (peripheral.connecting && !peripheral.bufferedConnect) { // add re-connect to buffer peripheral.connecting = false; peripheral.bufferedConnect = true; this.connect(peripheralUuid); } } } onMessage(event) { let { type, peripheralUuid, address, addressType, connectable, advertisement, rssi, serviceUuids, serviceUuid, includedServiceUuids, characteristics, characteristicUuid, isNotification, state, descriptors, descriptorUuid, handle } = event; const data = event.data ? Buffer.from(event.data, 'hex') : null; if (type === "auth") { // send authentication response if (typeof event.challenge != "undefined" && event.challenge.length == 32) { const challenge = crypto_js_1.default.enc.Hex.parse(event.challenge); const response = crypto_js_1.default.AES.encrypt(this.credentials, this.aesKey, { iv: challenge, mode: crypto_js_1.default.mode.CBC, padding: crypto_js_1.default.pad.ZeroPadding }); this.sendCommand({ action: "auth", response: response.toString(crypto_js_1.default.format.Hex) }); } } else if (type === 'stateChange') { if (state == "poweredOn" && !this.auth) { this.auth = true; if (this.buffer.length > 0) { if (process.env.WEBSOCKET_DEBUG == "1") { console.log("Sending buffered commands", this.buffer); } for (let command of this.buffer) { this.sendCommand(command); } this.buffer = []; } } if (!this.wasReady) { // only send state change once after the initial connection this.wasReady = true; this.emit('stateChange', state); } } else if (type === 'discover') { if (typeof advertisement != "undefined") { const advertisementObj = { localName: advertisement.localName, txPowerLevel: advertisement.txPowerLevel, serviceUuids: advertisement.serviceUuids, manufacturerData: (advertisement.manufacturerData ? Buffer.from(advertisement.manufacturerData, 'hex') : null), serviceData: (advertisement.serviceData ? Buffer.from(advertisement.serviceData, 'hex') : null) }; let peripheral = { uuid: peripheralUuid, address: address, advertisement: advertisementObj, rssi: rssi, connected: false, connecting: false, bufferedConnect: false }; this.peripherals.set(peripheralUuid, peripheral); this.emit('discover', peripheralUuid, address, addressType, connectable, advertisementObj, rssi); } } else if (type === 'connect') { const peripheral = this.peripherals.get(peripheralUuid); if (typeof peripheral != "undefined") { peripheral.connected = true; peripheral.connecting = false; peripheral.bufferedConnect = false; } this.emit('connect', peripheralUuid); } else if (type === 'disconnect') { const peripheral = this.peripherals.get(peripheralUuid); if (typeof peripheral != "undefined") { peripheral.connected = false; peripheral.connecting = false; peripheral.bufferedConnect = false; } this.emit('disconnect', peripheralUuid); } else if (type === 'rssiUpdate') { this.emit('rssiUpdate', peripheralUuid, rssi); } else if (type === 'servicesDiscover') { this.emit('servicesDiscover', peripheralUuid, serviceUuids); } else if (type === 'includedServicesDiscover') { this.emit('includedServicesDiscover', peripheralUuid, serviceUuid, includedServiceUuids); } else if (type === 'characteristicsDiscover') { this.emit('characteristicsDiscover', peripheralUuid, serviceUuid, characteristics); } else if (type === 'read') { this.emit('read', peripheralUuid, serviceUuid, characteristicUuid, data, isNotification); } else if (type === 'write') { this.emit('write', peripheralUuid, serviceUuid, characteristicUuid); } else if (type === 'broadcast') { this.emit('broadcast', peripheralUuid, serviceUuid, characteristicUuid, state); } else if (type === 'notify') { this.emit('notify', peripheralUuid, serviceUuid, characteristicUuid, state); } else if (type === 'descriptorsDiscover') { this.emit('descriptorsDiscover', peripheralUuid, serviceUuid, characteristicUuid, descriptors); } else if (type === 'valueRead') { this.emit('valueRead', peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data); } else if (type === 'valueWrite') { this.emit('valueWrite', peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid); } else if (type === 'handleRead') { this.emit('handleRead', peripheralUuid, handle, data); } else if (type === 'handleWrite') { this.emit('handleWrite', peripheralUuid, handle); } else if (type === 'handleNotify') { this.emit('handleNotify', peripheralUuid, handle, data); } } sendCommand(command, errorCallback) { if (!this.auth && command.action != "auth") { if (process.env.WEBSOCKET_DEBUG == "1") { console.log('Buffering command', command); } this.buffer.push(command); } else { const message = JSON.stringify(command); this.ws.send(message); if (process.env.WEBSOCKET_DEBUG == "1") { console.log("Sent: " + chalk_1.default.cyan(message)); } } } startScanning(serviceUuids, allowDuplicates = true) { this.startScanCommand = { action: 'startScanning', serviceUuids: serviceUuids, allowDuplicates: allowDuplicates }; this.sendCommand(this.startScanCommand); this.emit('scanStart'); } stopScanning() { this.startScanCommand = null; this.sendCommand({ action: 'stopScanning' }); this.emit('scanStop'); } connect(deviceUuid) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined" && !peripheral.connected && !peripheral.connecting) { peripheral.connecting = true; this.sendCommand({ action: 'connect', peripheralUuid: peripheral.uuid }); } } disconnect(deviceUuid) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'disconnect', peripheralUuid: peripheral.uuid }); } } updateRssi(deviceUuid) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'updateRssi', peripheralUuid: peripheral.uuid }); } } discoverServices(deviceUuid, uuids) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'discoverServices', peripheralUuid: peripheral.uuid, uuids: uuids }); } } discoverIncludedServices(deviceUuid, serviceUuid, serviceUuids) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'discoverIncludedServices', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, serviceUuids: serviceUuids }); } } discoverCharacteristics(deviceUuid, serviceUuid, characteristicUuids) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'discoverCharacteristics', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuids: characteristicUuids }); } } read(deviceUuid, serviceUuid, characteristicUuid) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'read', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid }); } } write(deviceUuid, serviceUuid, characteristicUuid, data, withoutResponse) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'write', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid, data: data.toString('hex'), withoutResponse: withoutResponse }); } } broadcast(deviceUuid, serviceUuid, characteristicUuid, broadcast) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'broadcast', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid, broadcast: broadcast }); } } notify(deviceUuid, serviceUuid, characteristicUuid, notify) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'notify', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid, notify: notify }); } } discoverDescriptors(deviceUuid, serviceUuid, characteristicUuid) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'discoverDescriptors', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid, }); } } readValue(deviceUuid, serviceUuid, characteristicUuid, descriptorUuid) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'readValue', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid, descriptorUuid: descriptorUuid }); } } writeValue(deviceUuid, serviceUuid, characteristicUuid, descriptorUuid, data) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'writeValue', peripheralUuid: peripheral.uuid, serviceUuid: serviceUuid, characteristicUuid: characteristicUuid, descriptorUuid: descriptorUuid, data: data.toString('hex') }); } } readHandle(deviceUuid, handle) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'readHandle', peripheralUuid: peripheral.uuid, handle: handle }); } } writeHandle(deviceUuid, handle, data, withoutResponse) { const peripheral = this.peripherals.get(deviceUuid); if (typeof peripheral != "undefined") { this.sendCommand({ action: 'writeHandle', peripheralUuid: peripheral.uuid, handle: handle, data: data.toString('hex'), withoutResponse: withoutResponse }); } } } exports.NobleWebsocketBinding = NobleWebsocketBinding;