diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 1ae2338..aa6a25d 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,45 @@ -# Android TV Remote Node Module +# @cldmv/node-android-tv-remote -[Keyboard Keys](./docs/KEYBOARD_KEYS.md) | [Keycodes](./docs/KEYCODES.md) +**@cldmv/node-android-tv-remote** is a modern, **event-driven** Node.js module for controlling Android TV devices via ADB. It supports sending keycodes, comprehensive keyboard input (71 characters with smart shift detection), remote control commands, screenshots and device management. It is designed for compatibility with a wide range of Android TV devices, including Fire TV, Chromecast with Google TV, Nvidia Shield and more. ---- +Every remote is an event source: operations report through structured `log` events and failures through structured `error` events instead of writing to the console, so the library slots into an application's own logging and error handling. + +> _Drive any Android TV from Node.js — keys, text, screenshots and power — over ADB._ -## Table of Contents +[![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] [![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] [![npm last update]][npm_last_update_url] [![coverage]][coverage_url] -- [Overview](#overview) -- [Installation](#installation) -- [Usage](#usage) -- [API](#api) -- [Supported Devices](#supported-devices) -- [Keyboard Keys](./docs/KEYBOARD_KEYS.md) -- [Keycodes](./docs/KEYCODES.md) +[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url] --- -## Overview +## ✨ What's New + +### Latest: v2.1.8 (October 2026) + +- **Independent remotes, and no crash on an unheard error** — each remote now has its own event emitter, so two TVs no longer receive each other's events. An `error` with no listener is logged (a `log` event with level `"error"`, and `NODE_DEBUG=android-tv-remote`) instead of thrown, while the failing call still rejects ([#46](https://github.com/CLDMV/node-android-tv-remote/pull/46)). +- **`npm run setup-device` works again** — the script, broken since v2.1.0, now runs the setup steps through the v2 remote ([#45](https://github.com/CLDMV/node-android-tv-remote/pull/45)). The package is also relicensed under Apache-2.0 ([#41](https://github.com/CLDMV/node-android-tv-remote/pull/41)). +- **Header tooling on fix-headers 2.2.0** — the `@cldmv/fix-headers` and `@cldmv/configs` dev dependencies move to 2.2.0 and 1.2.4, so `@Last modified by` now follows content edits only; no file was restamped ([#50](https://github.com/CLDMV/node-android-tv-remote/pull/50), [#52](https://github.com/CLDMV/node-android-tv-remote/pull/52)). +- [View full v2.1.8 Changelog](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/changelog/v2/v2.1.8.md) -A modern, **event-driven** Node.js module for controlling Android TV devices via ADB. Supports sending keycodes, comprehensive keyboard input (71 characters with smart shift detection), and remote control commands. Designed for compatibility with a wide range of Android TV devices, including Fire TV, Chromecast with Google TV, and more. +### Recent Releases -### Key Features +- **v2.1.7** (October 2026) — CommonJS entry loads the ESM build directly, fails clearly without `require(esm)`, and exports `createAndroidTVRemote` ([Changelog](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/changelog/v2/v2.1.7.md)) +- **v2.1.6** (October 2026) — CI only: the in-repo PR mirror job runs instead of being skipped; `sharp` lockfile and `@cldmv/vitest-runner` bumps ([Changelog](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/changelog/v2/v2.1.6.md)) +- **v2.1.5** (October 2026) — maintenance: v4.29.2 workflow sync with bundle-size measurement, required-check mirror fix, uniform file headers; no runtime change ([Changelog](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/changelog/v2/v2.1.5.md)) +- **v2.1.4** (September 2026) — `@devicefarmer/adbkit` 3.3.9 in the lockfile, dead code removed from the screencap path, bot signing secrets wired into the release workflows ([Changelog](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/changelog/v2/v2.1.4.md)) + +> **Note:** v2.1.1 through v2.1.6 were released on GitHub but never published to npm; npm went from v2.1.0 straight to v2.1.7. See the changelogs for what changed in between, including the Node.js 20.9.0 floor from v2.1.1. + +📚 **For complete version history and detailed release notes, see the [docs/changelog/](https://github.com/CLDMV/node-android-tv-remote/tree/master/docs/changelog/) folder.** + +--- + +## 🚀 Key Features - 🎯 **Event-driven architecture** - Comprehensive event system with structured data - ⌨️ **Comprehensive keyboard input** - 71 characters with smart shift detection and special symbols - 🔄 **ESM & CommonJS support** - Works with both `import` and `require` -- 🛡️ **Error resilience** - Errors emit events instead of crashing your app +- 🛡️ **Error resilience** - Errors are reported as events instead of crashing your app; an `error` listener is optional (see [Event-Driven Usage](#event-driven-usage-recommended)) - 🔗 **Method chaining** - Fluent API for event listener management - 📊 **Structured logging** - Timestamped, categorized log events with source tracking - 🚀 **Promise-based** - Modern async/await support with callback compatibility @@ -34,9 +48,22 @@ A modern, **event-driven** Node.js module for controlling Android TV devices via - 🔄 **Device management** - Reboot, wake, settings configuration with event tracking - 📱 **Universal compatibility** - Works with Fire TV, Chromecast, Shield, and more -## Installation +--- + +## 📦 Installation + +### Requirements + +- **Node.js 20.9.0 or higher** for ESM `import` (the package's `engines` floor, set by the `sharp` dependency). +- **`require()`** loads the ESM build through Node's `require(esm)`, so it needs **Node.js ^20.19.0 or >=22.12.0**. On older Node.js, load the package with `import()` instead. +- The **Android Debug Bridge (ADB)** binary installed and available in your system PATH (see below). A `postinstall` check prints install instructions when `adb` is missing. +- `sharp` installs a platform-specific prebuilt binary; the install environment must be able to fetch it. -This module requires the Android Debug Bridge (ADB) binary to be installed and available in your system PATH. +### Install + +```sh +npm install @cldmv/node-android-tv-remote +``` ### Install ADB @@ -67,22 +94,16 @@ This module requires the Android Debug Bridge (ADB) binary to be installed and a > **Note:** You must accept the ADB authorization prompt on your Android TV device the first time you connect. -### Install the Node Module - -```sh -npm install android-tv-remote -``` - -## Usage +--- -### Basic Usage +## 🚀 Quick Start ```js // ESM (Node.js with type: "module" in package.json) -import createRemote from "android-tv-remote"; +import createRemote from "@cldmv/node-android-tv-remote"; // CommonJS -const createRemote = require("android-tv-remote"); +const createRemote = require("@cldmv/node-android-tv-remote"); // Create remote (async function) const remote = await createRemote({ ip: "192.168.1.100" }); @@ -93,18 +114,32 @@ await remote.press.up(); await remote.press.ok(); // Or chain promises -createRemote({ ip: "192.168.1.100" }) - .then((remote) => remote.press.home()) - .then(() => remote.press.up()) - .then(() => remote.press.ok()); +createRemote({ ip: "192.168.1.100" }).then(async (remote) => { + await remote.press.home(); + await remote.press.up(); + await remote.press.ok(); +}); ``` +`createAndroidTVRemote(config)` is an alias of `createRemote(config)`, exported by name from both entries: + +```js +import { createAndroidTVRemote } from "@cldmv/node-android-tv-remote"; +// or: const { createAndroidTVRemote } = require("@cldmv/node-android-tv-remote"); + +const remote = await createAndroidTVRemote({ ip: "192.168.1.100" }); +``` + +--- + +## 🎯 Usage + ### Event-Driven Usage (Recommended) -This module uses an **event-driven architecture** instead of console logging. All operations emit structured events that you can listen to: +This module uses an **event-driven architecture** instead of console logging. All operations emit structured events that you can listen to. The remote is a Node.js event emitter, but an `error` event is only emitted when a listener is attached, so an `error` listener is optional. Without one, the error is emitted as a `log` event with `level: 'error'` and written to `NODE_DEBUG=android-tv-remote`. Register an `error` listener to handle failures yourself: ```js -import createRemote from "android-tv-remote"; +import createRemote from "@cldmv/node-android-tv-remote"; const remote = await createRemote({ ip: "192.168.1.100" }); @@ -138,7 +173,7 @@ await remote.screencap({ filepath: "./screenshot.png" }); The createRemote function is async and returns a Promise: ```js -import createRemote from "android-tv-remote"; +import createRemote from "@cldmv/node-android-tv-remote"; // Async initialization with await const remote = await createRemote({ ip: "192.168.1.100" }); @@ -157,7 +192,7 @@ createRemote({ ip: "192.168.1.100" }).then((remote) => { Advanced screencap with resizing, thumbnails, and file saving: ```js -import createRemote from "android-tv-remote"; +import createRemote from "@cldmv/node-android-tv-remote"; const remote = await createRemote({ ip: "192.168.1.100" }); @@ -201,7 +236,7 @@ remote.on("screencap-complete", (data) => { Comprehensive device control with event tracking: ```js -import createRemote from "android-tv-remote"; +import createRemote from "@cldmv/node-android-tv-remote"; const remote = await createRemote({ ip: "192.168.1.100" }); @@ -239,7 +274,7 @@ remote.on("log", (data) => { Comprehensive text input with individual key support and smart shift detection: ```js -import createRemote from "android-tv-remote"; +import createRemote from "@cldmv/node-android-tv-remote"; const remote = await createRemote({ ip: "192.168.1.100" }); @@ -324,7 +359,9 @@ await remote.keyboard.key.a.keycode(); // Sends keycode instead of character } ``` -## API +--- + +## 🔧 API ### Main Methods @@ -351,7 +388,7 @@ await remote.keyboard.key.a.keycode(); // Sends keycode instead of character ### Events - `log` - Emitted for all operations (info, warn, error, debug levels) -- `error` - Emitted when errors occur (structured error data) +- `error` - Emitted when errors occur (structured error data). Each remote has its own listeners. With no `error` listener attached, the error is emitted as a `log` event with `level: 'error'` (the `Error` is in `data.error`) instead of throwing. The failing call still reports it: commands reject, and `connect()` / `disconnect()` resolve with the `Error` as before - `screencap-start` - Emitted when screenshot capture begins - `screencap-captured` - Emitted when raw screenshot is captured - `screencap-processing` - Emitted when image processing begins @@ -367,7 +404,9 @@ await remote.keyboard.key.a.keycode(); // Sends keycode instead of character See JSDoc comments in source code for full API documentation. -## Supported Devices +--- + +## 📺 Supported Devices | Device | Supported | Native Remote Buttons | | ------------------------ | --------- | ------------------------------------------- | @@ -378,10 +417,74 @@ See JSDoc comments in source code for full API documentation. | Xiaomi Mi Box | Yes | Home, Back, D-Pad, Volume | | Sony Bravia (Android TV) | Yes | Home, Back, D-Pad, Volume, Input | -See [Keyboard Keys](./docs/KEYBOARD_KEYS.md) and [Keycodes](./docs/KEYCODES.md) for full lists. +See [Keyboard Keys](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/KEYBOARD_KEYS.md) and [Keycodes](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/KEYCODES.md) for full lists. + +--- + +## 📚 Documentation + +- **[Keyboard Keys](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/KEYBOARD_KEYS.md)** — every `keyboard.key` character and its shifted variant +- **[Keycodes](https://github.com/CLDMV/node-android-tv-remote/blob/master/docs/KEYCODES.md)** — the Android keycodes behind `press.()` and `inputKeycode()` +- **[Changelog](https://github.com/CLDMV/node-android-tv-remote/tree/master/docs/changelog/)** — release notes for every version + +[![CodeFactor]][codefactor_url] [![OpenSSF Scorecard]][ossf_scorecard_url] [![npms.io score]][npms_url] [![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url] + +--- + +## 🤝 Contributing + +Contributions are welcome — open an issue or a pull request on [GitHub](https://github.com/CLDMV/node-android-tv-remote). + +```bash +npm test # Vitest, then the CommonJS entry tests +npm run lint # ESLint +npm run format # Prettier +``` + +[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url] --- -## License +## 🔗 Links + +- **npm**: [@cldmv/node-android-tv-remote](https://www.npmjs.com/package/@cldmv/node-android-tv-remote) +- **GitHub**: [CLDMV/node-android-tv-remote](https://github.com/CLDMV/node-android-tv-remote) +- **Issues**: [GitHub Issues](https://github.com/CLDMV/node-android-tv-remote/issues) +- **Changelog**: [docs/changelog/](https://github.com/CLDMV/node-android-tv-remote/tree/master/docs/changelog/) + +--- -MIT +## 📄 License + +[![npm license]][npm_license_url] + +Apache-2.0 © Shinrai / CLDMV. See [LICENSE](https://github.com/CLDMV/node-android-tv-remote/blob/master/LICENSE) for the full text. + +[npm version]: https://img.shields.io/npm/v/%40cldmv%2Fnode-android-tv-remote.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_version_url]: https://www.npmjs.com/package/@cldmv/node-android-tv-remote +[last commit]: https://img.shields.io/github/last-commit/CLDMV/node-android-tv-remote?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[last_commit_url]: https://github.com/CLDMV/node-android-tv-remote/commits +[npm last update]: https://img.shields.io/npm/last-update/%40cldmv%2Fnode-android-tv-remote?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_last_update_url]: https://www.npmjs.com/package/@cldmv/node-android-tv-remote +[codefactor]: https://img.shields.io/codefactor/grade/github/CLDMV/node-android-tv-remote?style=for-the-badge&logo=codefactor&logoColor=white&labelColor=F44A6A +[codefactor_url]: https://www.codefactor.io/repository/github/cldmv/node-android-tv-remote +[openssf scorecard]: https://img.shields.io/ossf-scorecard/github.com/CLDMV/node-android-tv-remote?style=for-the-badge&label=OpenSSF%20Scorecard +[ossf_scorecard_url]: https://scorecard.dev/viewer/?uri=github.com/CLDMV/node-android-tv-remote +[npms.io score]: https://img.shields.io/npms-io/final-score/%40cldmv%2Fnode-android-tv-remote?style=for-the-badge&logo=npms&logoColor=white&labelColor=0B5D57 +[npms_url]: https://npms.io/search?q=%40cldmv%2Fnode-android-tv-remote +[npm downloads]: https://img.shields.io/npm/dm/%40cldmv%2Fnode-android-tv-remote.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_downloads_url]: https://www.npmjs.com/package/@cldmv/node-android-tv-remote +[github downloads]: https://img.shields.io/github/downloads/CLDMV/node-android-tv-remote/total?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[github_downloads_url]: https://github.com/CLDMV/node-android-tv-remote/releases +[npm unpacked size]: https://img.shields.io/npm/unpacked-size/%40cldmv%2Fnode-android-tv-remote.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_size_url]: https://www.npmjs.com/package/@cldmv/node-android-tv-remote +[repo size]: https://img.shields.io/github/repo-size/CLDMV/node-android-tv-remote?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[repo_size_url]: https://github.com/CLDMV/node-android-tv-remote +[npm license]: https://img.shields.io/npm/l/%40cldmv%2Fnode-android-tv-remote.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_license_url]: https://www.npmjs.com/package/@cldmv/node-android-tv-remote +[coverage]: https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FCLDMV%2Fnode-android-tv-remote%2Fbadges%2Fcoverage.json&style=for-the-badge&logo=vitest&logoColor=white +[coverage_url]: https://github.com/CLDMV/node-android-tv-remote/blob/badges/coverage.json +[contributors]: https://img.shields.io/github/contributors/CLDMV/node-android-tv-remote.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[contributors_url]: https://github.com/CLDMV/node-android-tv-remote/graphs/contributors +[sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor +[sponsor_url]: https://github.com/sponsors/shinrai diff --git a/docs/changelog/v1/v1.0.0.md b/docs/changelog/v1/v1.0.0.md new file mode 100644 index 0000000..a0ff5c6 --- /dev/null +++ b/docs/changelog/v1/v1.0.0.md @@ -0,0 +1,65 @@ +# @cldmv/node-android-tv-remote v1.0.0 Changelog + +**Release Date**: July 2025 +**Release Type**: Initial release + +--- + +## Overview + +Version 1.0.0 is the first release of `@cldmv/node-android-tv-remote`, a Node.js library for controlling Android TV and Fire TV devices over ADB. It exposes a single factory function that returns a remote with connection management, remote-control key presses, long presses, and keyboard input, built on `adbkit`. + +--- + +## ✨ Features + +### Remote factory + +`require("@cldmv/node-android-tv-remote")` exports a factory function that takes a `RemoteConfig` and returns a remote. Supported options are `ip`, `port` (default `5555`), `inputDevice` (default `/dev/input/event0`, used for long presses), `autoConnect` (default `true`), `autoDisconnect` (default `true`), `disconnectTimeout` (seconds of inactivity before auto-disconnect, default `10`), and `quiet` (default `true`, suppresses connection logging). + +### Connection management + +- `connect()` and `disconnect()` support both promise and Node-style callback usage. +- Commands connect on demand when `autoConnect` is enabled, and the inactivity timer disconnects after `disconnectTimeout` seconds when `autoDisconnect` is enabled. +- An "already connected" / "already disconnected" response from ADB is treated as success rather than an error. +- Connection failures print guidance for unauthorized devices (accept the authorization prompt on the TV). + +### Remote control keys + +`press.()` sends the matching Android keycode for each entry in `src/data/remote-keys.json`: `home`, `back`, `menu`, `ok`, `select`, `up`, `down`, `left`, `right`, `play`, `pause`, `playPause`, `stop`, `next`, `previous`, `fastForward`, `rewind`, `volumeUp`, `volumeDown`, `volumeMute`, `channelUp`, `channelDown`, `info`, `guide`, `settings`, `apps`, `caption`, `bookmark`, `help`, `power`, `input`, `mute`, `search`, and `number0` through `number9`. `press.long.()` sends the same key as a long press via `sendevent` against the configured `inputDevice`. + +### Keyboard input + +- `keyboard.text(text)` types a string with `input text`. +- `keyboard.key(name, { forceKeycode })` and `keyboard.key.()` send a single key, using text input for single characters and falling back to the Android keycode otherwise. `keyboard.key..keycode()` always sends the keycode. +- `keyboard.key.shift.()` and `keyboard.key.shift..keycode()` send the shifted variant. +- `inputKeycode(code)` sends a raw Android keycode. + +### Device settings helper + +`handleSettings(mode, [overrideQuiet])` reads (`"get"`) or writes (`"set"`) the `screen_off_timeout`, `sleep_timeout`, and `stay_on_while_plugged_in` Android settings so the device stays awake. + +### ADB install check + +A `postinstall` script checks for `adb` on the `PATH` and prints install instructions for Windows, macOS, and Linux when it is missing. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.0.0.md](./v1.0.0.md) — this changelog. +- README with ADB installation instructions, usage, API summary, and supported-device table. +- `docs/KEYBOARD_KEYS.md` and `docs/KEYCODES.md` key reference lists. + +--- + +## 🔧 Dependencies + +- `adbkit` 2.11.1 +- `jest` ^30.0.5 (dev) + +--- + +## Upgrade notes + +Initial release — nothing to upgrade from. diff --git a/docs/changelog/v1/v1.1.0.md b/docs/changelog/v1/v1.1.0.md new file mode 100644 index 0000000..656b3fa --- /dev/null +++ b/docs/changelog/v1/v1.1.0.md @@ -0,0 +1,49 @@ +# @cldmv/node-android-tv-remote v1.1.0 Changelog + +**Release Date**: July 2025 +**Release Type**: Minor + +--- + +## Overview + +Version 1.1.0 makes the directional keys work reliably, improves the guidance printed when an ADB connection fails, and adds an interactive device-setup script. + +No breaking changes. All v1.0.0 configuration and usage is fully compatible. + +--- + +## ✨ Features + +### `press.up/down/left/right` map to the D-pad keycodes + +`press.up()`, `press.down()`, `press.left()`, and `press.right()` (and their `press.long.*` forms) now send `dpadUp`, `dpadDown`, `dpadLeft`, and `dpadRight` explicitly. In v1.0.0 these looked up keycodes named `up`/`down`/`left`/`right` directly. + +### Better connection-failure guidance + +- Authentication failures (`failed to authenticate`, in addition to `device unauthorized`) now print extra recovery steps: reconnect or reboot the TV, remove the device from the authorized ADB list, and toggle ADB Debugging off and on. +- A refused connection (`actively refused` / `No connection could be made`) prints the steps for enabling Developer Options and ADB Debugging on Android TV and Fire TV. + +### Interactive device setup + +- `npm run setup-device` runs `scripts/setup-device.js`, which prompts for the device IP and port, applies the keep-awake settings, ensures the device is awake, and returns it to the home screen. +- `src/lib/adb/setup.js` adds an `AndroidTVSetup` helper used by the setup script and the test scripts. It is an internal helper, not part of the package's main export. + +--- + +## 🔧 CI & tooling + +- `test/connect-readall.js` now drives `AndroidTVSetup` instead of carrying its own copy of the settings logic. +- New `test/interactive-remote.js` script for manually exercising the remote buttons against a device. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.1.0.md](./v1.1.0.md) — this changelog. + +--- + +## Upgrade notes + +No breaking changes — drop-in for v1.0.0. diff --git a/docs/changelog/v1/v1.2.0.md b/docs/changelog/v1/v1.2.0.md new file mode 100644 index 0000000..ac95688 --- /dev/null +++ b/docs/changelog/v1/v1.2.0.md @@ -0,0 +1,58 @@ +# @cldmv/node-android-tv-remote v1.2.0 Changelog + +**Release Date**: July 2025 +**Release Type**: Minor + +--- + +## Overview + +Version 1.2.0 adds a persistent, self-healing ADB connection (heartbeat plus periodic connection checks) and connection-status helpers. It also changes two defaults in a way that is not backward compatible, even though it shipped as a minor release. + +--- + +## ✨ Features + +### Persistent connection with heartbeat and auto-reconnect + +New `RemoteConfig` options: + +- `maintainConnection` (default `true`) — keep the ADB connection alive while connected. +- `heartbeatInterval` (default `30000` ms) — how often a no-op shell command (`echo heartbeat`) is sent. +- `connectionCheckInterval` (default `30000` ms) — how often the device list is checked; if the device has dropped out of `adb devices`, the remote attempts to reconnect. + +The timers start on `connect()` and stop on `disconnect()`. + +### Connection status helpers + +- `getConnectionStatus([liveCheck])` resolves to `"connected"` or `"disconnected"` from internal state, or, with `liveCheck` set to `true`, queries `adb devices` and can also resolve to `"unknown"` if the query fails. +- `isConnected` reports the module's internal connection state without a live check. It is exposed as a function in this release, so call it as `remote.isConnected()`. + +--- + +## 💥 Breaking Changes + +This release shipped as a minor version but changes default behaviour. + +- **`autoDisconnect` now defaults to `false`.** In v1.0.0 and v1.1.0 the connection was dropped after `disconnectTimeout` seconds of inactivity unless disabled; now it stays open unless you pass `autoDisconnect: true`. +- **The remote now keeps timers running while connected.** With `maintainConnection` on by default, the heartbeat and connection-check intervals stay active after `connect()`, which can keep a Node process from exiting on its own until `disconnect()` is called. + +Upgrade steps: pass `autoDisconnect: true` to keep the previous idle-disconnect behaviour, pass `maintainConnection: false` to disable the heartbeat and reconnect timers, and make sure scripts call `remote.disconnect()` before they are expected to exit. + +--- + +## 🔧 CI & tooling + +- `test/interactive-remote.js` now uses the main remote module instead of the `AndroidTVSetup` helper. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.2.0.md](./v1.2.0.md) — this changelog. + +--- + +## Upgrade notes + +Not a pure drop-in for v1.1.0: review the `autoDisconnect` default and the new keep-alive timers described under Breaking Changes. Code that sets `autoDisconnect` explicitly and calls `disconnect()` when finished is unaffected. diff --git a/docs/changelog/v1/v1.3.0.md b/docs/changelog/v1/v1.3.0.md new file mode 100644 index 0000000..8e8ae26 --- /dev/null +++ b/docs/changelog/v1/v1.3.0.md @@ -0,0 +1,36 @@ +# @cldmv/node-android-tv-remote v1.3.0 Changelog + +**Release Date**: July 2025 +**Release Type**: Minor + +--- + +## Overview + +Version 1.3.0 expands the `press` interface with numpad, paging, and media keys. + +No breaking changes. All v1.2.0 configuration and usage is fully compatible. + +--- + +## ✨ Features + +### Numpad, paging, and media keys on `press` + +`src/data/remote-keys.json` gains 34 entries, each available as `press.()` and `press.long.()`: + +- Numpad: `numpad0` through `numpad9`, `numpadDivide`, `numpadMultiply`, `numpadSubtract`, `numpadAdd`, `numpadDot`, `numpadComma`, `numpadEnter`, `numpadEquals`, `numpadLeftParen`, `numpadRightParen` +- Paging: `pageUp`, `pageDown` +- Media: `mediaPlay`, `mediaPause`, `mediaClose`, `mediaEject`, `mediaRecord`, `mediaSkipForward`, `mediaSkipBackward`, `mediaStepForward`, `mediaStepBackward`, `mediaAudioTrack`, `mediaTopMenu` + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.3.0.md](./v1.3.0.md) — this changelog. + +--- + +## Upgrade notes + +No breaking changes — drop-in for v1.2.0. diff --git a/docs/changelog/v1/v1.4.0.md b/docs/changelog/v1/v1.4.0.md new file mode 100644 index 0000000..307121f --- /dev/null +++ b/docs/changelog/v1/v1.4.0.md @@ -0,0 +1,39 @@ +# @cldmv/node-android-tv-remote v1.4.0 Changelog + +**Release Date**: July 2025 +**Release Type**: Minor + +--- + +## Overview + +Version 1.4.0 makes the remote aware of a device that is already connected, so creating a remote against a connected device no longer starts from a stale "disconnected" state. + +No breaking changes. All v1.3.0 configuration and usage is fully compatible. + +--- + +## ✨ Features + +### Already-connected detection + +- When a remote is created, it runs a live `getConnectionStatus(true)` check; if the device is already connected, internal state is set to connected and the heartbeat and connection-check timers start. +- `connect()` now returns immediately when the remote already believes it is connected, instead of calling ADB again. + +--- + +## 🔧 CI & tooling + +- `test/connect-readall.js` checks whether the setup helper is already connected before calling `connect()`. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.4.0.md](./v1.4.0.md) — this changelog. + +--- + +## Upgrade notes + +No breaking changes — drop-in for v1.3.0. diff --git a/docs/changelog/v1/v1.5.0.md b/docs/changelog/v1/v1.5.0.md new file mode 100644 index 0000000..437cafd --- /dev/null +++ b/docs/changelog/v1/v1.5.0.md @@ -0,0 +1,33 @@ +# @cldmv/node-android-tv-remote v1.5.0 Changelog + +**Release Date**: July 2025 +**Release Type**: Minor + +--- + +## Overview + +Version 1.5.0 adds two introspection methods to the remote returned by `createRemote()`, so callers can discover which keyboard keys and press commands the live API exposes. Two new key names (`wakeup`, `enter`) are also added to the press command list. + +No breaking changes. All v1.4.0 configuration and usage is fully compatible. + +--- + +## ✨ Features + +- **`remote.getKeyboardKeys()`** — returns the names of every function on `remote.keyboard.key` (for example `"a"`, `"enter"`, `"space"`). It inspects the live object, so it always matches the real API surface, and it excludes the `keycode` and `shift` sub-objects. +- **`remote.getPressCommands()`** — returns the names of every press command on `remote.press`, including aliases, excluding `long`. +- **New press keys `wakeup` and `enter`** added to `src/data/remote-keys.json`. +- **API surface test** — `test/api-surface.test.js` covers both new methods. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.5.0.md](./v1.5.0.md) — this changelog. + +--- + +## Upgrade notes + +No breaking changes — drop-in for v1.4.0. diff --git a/docs/changelog/v1/v1.5.1.md b/docs/changelog/v1/v1.5.1.md new file mode 100644 index 0000000..35cfec1 --- /dev/null +++ b/docs/changelog/v1/v1.5.1.md @@ -0,0 +1,72 @@ +# @cldmv/node-android-tv-remote v1.5.1 Changelog + +**Release Date**: July 2025 +**Release Type**: Patch + +--- + +## Overview + +Version 1.5.1 adds five streaming-service keycodes and a `recall` press key, validates the `ip` option up front, and fixes the order of initialization inside the `createRemote()` factory. It also changes `isConnected` from a method to a plain property, which breaks callers of `remote.isConnected()` despite the patch version number — see Breaking Changes. + +--- + +## 💥 Breaking Changes + +### `isConnected` stopped being a method (shipped despite being a patch) + +In v1.5.0 `remote.isConnected` was a function; in v1.5.1 it is the boolean returned by `isConnected()` evaluated once, when `createRemote()` runs. Calling `remote.isConnected()` now throws `TypeError: remote.isConnected is not a function`, and the property is a fixed snapshot (`false` at creation) that never updates. v1.5.2 turns it into a live getter, but it remains a property, so the call form stays broken. + +Before (v1.5.0): + +```js +if (remote.isConnected()) { + // ... +} +``` + +After (v1.5.2 or later): + +```js +if (remote.isConnected) { + // ... +} +``` + +For a check against the device itself, use `await remote.getConnectionStatus(true)`. + +### `createRemote()` throws without `ip` + +The factory now throws `Error("Missing required 'ip' property in RemoteConfig.")` when called with no config, a non-object, or a config without `ip`. + +Before: `createRemote()` and `createRemote({})` were accepted. + +After: always pass `{ ip: "192.168.1.50" }` (plus any other options). + +--- + +## ✨ Features + +- **New keycodes** `azmNetflix` (290), `azmPrime` (291), `azmDirecttv` (292), `azmPeacock` (296) and `azmGuide` (297) in `src/data/keycodes.json`, exposed as press keys in `remote-keys.json`. +- **New press key `recall`.** +- **Type checking** — `src/lib/android-tv-remote.js` is now checked with `// @ts-check`, with typed `connect`, `disconnect` and `inputKeycode` signatures and usage examples in the JSDoc. + +--- + +## 🐛 Bug Fixes + +### Initial connection check runs after configuration is read + +The on-creation "already connected" check was declared at the top of the factory, before the connection state and options it reads had been set up, so its errors were swallowed by the surrounding `try`/`catch` and the check never took effect. It now runs after the configuration is parsed. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.5.1.md](./v1.5.1.md) — this changelog. + +--- + +## Upgrade notes + +Replace every `remote.isConnected()` call with the property form `remote.isConnected` and make sure `ip` is always supplied. Move straight to v1.5.2 so the property reflects live state. diff --git a/docs/changelog/v1/v1.5.2.md b/docs/changelog/v1/v1.5.2.md new file mode 100644 index 0000000..aea3411 --- /dev/null +++ b/docs/changelog/v1/v1.5.2.md @@ -0,0 +1,54 @@ +# @cldmv/node-android-tv-remote v1.5.2 Changelog + +**Release Date**: July 2025 +**Release Type**: Patch + +--- + +## Overview + +Version 1.5.2 makes `isConnected` a getter property, so it reflects the current connection state instead of the snapshot taken at creation in v1.5.1. It is a one-line change, but it settles the property form that broke `remote.isConnected()` callers in v1.5.1. + +--- + +## 💥 Breaking Changes + +### `isConnected` is a getter, not a method (shipped despite being a patch) + +`remote.isConnected` is now a getter that returns the module's tracked connection state. Code written against v1.5.0 or earlier that calls it as a function fails with `TypeError: remote.isConnected is not a function`. + +Before (v1.5.0 and earlier): + +```js +if (remote.isConnected()) { + // ... +} +``` + +After: + +```js +if (remote.isConnected) { + // ... +} +``` + +The value is the internal state, not a live check. `await remote.getConnectionStatus(true)` queries `adb devices`. + +--- + +## 🐛 Bug Fixes + +- `remote.isConnected` now tracks connect and disconnect events over the life of the remote instead of staying at its creation-time value. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.5.2.md](./v1.5.2.md) — this changelog. + +--- + +## Upgrade notes + +Drop the parentheses on every `isConnected` call. Otherwise drop-in for v1.5.1. diff --git a/docs/changelog/v1/v1.5.3.md b/docs/changelog/v1/v1.5.3.md new file mode 100644 index 0000000..17c16ae --- /dev/null +++ b/docs/changelog/v1/v1.5.3.md @@ -0,0 +1,36 @@ +# @cldmv/node-android-tv-remote v1.5.3 Changelog + +**Release Date**: July 2025 +**Release Type**: Patch + +--- + +## Overview + +Version 1.5.3 keeps the internal connection state in sync when a live connection check runs, and fixes the interactive test script to use the factory API. + +No breaking changes. All v1.5.2 configuration and usage is fully compatible. + +--- + +## 🐛 Bug Fixes + +### `getConnectionStatus(true)` updates the tracked state + +A live check (`await remote.getConnectionStatus(true)`) now sets the internal connected flag to match what `adb devices` reports, and clears it if the check fails. Before, a live check could report `"connected"` or `"disconnected"` while `remote.isConnected` kept its previous value. + +### Interactive test script + +`test/interactive-remote.js` now calls `createRemote({ ip, port })` and then `remote.connect()`, instead of calling `connect({ ip, port })` on the module export. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.5.3.md](./v1.5.3.md) — this changelog. + +--- + +## Upgrade notes + +No breaking changes — drop-in for v1.5.2. diff --git a/docs/changelog/v1/v1.5.4.md b/docs/changelog/v1/v1.5.4.md new file mode 100644 index 0000000..c036017 --- /dev/null +++ b/docs/changelog/v1/v1.5.4.md @@ -0,0 +1,51 @@ +# @cldmv/node-android-tv-remote v1.5.4 Changelog + +**Release Date**: July 2025 +**Release Type**: Patch + +--- + +## Overview + +Version 1.5.4 changes how connection errors are surfaced. `connect()` and `disconnect()` no longer terminate the host process for every failure, and they now resolve with values. Because those return values and error paths changed in a patch release, callers that relied on the old behavior should read Breaking Changes. + +--- + +## 💥 Breaking Changes + +### Connection errors no longer always call `process.exit(1)` (shipped despite being a patch) + +Through v1.5.3, any error handled by the internal `handleDisconnectError()` ended with `process.exit(1)`. In v1.5.4 only the two cases with onboarding instructions still exit the process: an unauthorized or unauthenticated device, and a refused connection. Every other connect or disconnect error is logged and the promise resolves with the `Error` object itself rather than rejecting. + +Before: a failed `connect()` with, say, a timeout killed the process, so code after `await remote.connect()` never ran on failure. + +After: `await remote.connect()` can return with `connected` still false, and the resolved value is an `Error`. + +```js +const result = await remote.connect(); +if (result instanceof Error) { + // handle the failure; the promise did not reject +} +``` + +### `connect()` and `disconnect()` resolve `true` in the already-connected and already-disconnected cases + +Those two branches previously resolved `undefined`; they now resolve `true`. A normal successful `connect()` still resolves `undefined`. Code that compared the result to `undefined` should be updated. + +--- + +## ✨ Features + +- **`scripts/test-keycodes.js`** — a helper script for exercising keycodes against a device. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.5.4.md](./v1.5.4.md) — this changelog. + +--- + +## Upgrade notes + +Audit anything that awaits `connect()` or `disconnect()`: check the resolved value for an `Error`, and do not assume a failure stops the process. diff --git a/docs/changelog/v1/v1.5.5.md b/docs/changelog/v1/v1.5.5.md new file mode 100644 index 0000000..e8f5a74 --- /dev/null +++ b/docs/changelog/v1/v1.5.5.md @@ -0,0 +1,53 @@ +# @cldmv/node-android-tv-remote v1.5.5 Changelog + +**Release Date**: July 2025 +**Release Type**: Patch + +--- + +## Overview + +Version 1.5.5 exposes `remote.initPromise`, which settles with the result of the connection attempt made when `createRemote()` runs. That attempt now connects the device if it is not already connected. Because it runs at creation and can reject, treat this as a behavior change despite the patch number. + +--- + +## 💥 Breaking Changes + +### Initialization connects automatically, and a failed attempt rejects `initPromise` (shipped despite being a patch) + +When `createRemote()` is called it checks the device with a live status check. In v1.5.4 and earlier a "not connected" result did nothing and errors were ignored. In v1.5.5 a not-connected result calls `connect()`, and the outcome goes to `initPromise`: it resolves when connected and rejects with `Error("Failed to connect to device on initialization.")` (or the underlying error) otherwise. This runs regardless of the `autoConnect` option. + +A rejected promise with no handler is an unhandled rejection, which terminates the process on current Node.js versions. Callers that create a remote while the device is unreachable should attach a handler: + +```js +const remote = createRemote({ ip: "192.168.1.50" }); +remote.initPromise.catch((err) => { + console.error("TV not reachable:", err.message); +}); +``` + +--- + +## ✨ Features + +- **`remote.initPromise`** — `Promise` that resolves if the initial connection succeeds and rejects if it fails. + +```js +remote.initPromise + .then(() => remote.press.home()) + .catch((err) => { + /* ... */ + }); +``` + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.5.5.md](./v1.5.5.md) — this changelog. + +--- + +## Upgrade notes + +Always attach a `.catch()` to `remote.initPromise` (or `await` it inside `try`/`catch`) whenever the device may be offline when the remote is created. diff --git a/docs/changelog/v2/v2.0.0.md b/docs/changelog/v2/v2.0.0.md new file mode 100644 index 0000000..058d75f --- /dev/null +++ b/docs/changelog/v2/v2.0.0.md @@ -0,0 +1,78 @@ +# @cldmv/node-android-tv-remote v2.0.0 Changelog + +**Release Date**: October 2025 +**Release Type**: Major + +--- + +## Overview + +Version 2.0.0 turns the library into an ES module with a CommonJS entry, moves from the unmaintained `adbkit` 2.x to `@devicefarmer/adbkit` 3.x, and replaces console output with an event-driven model. Every remote is now an event source: operations report through structured `log` events and failures through structured `error` events, instead of printing to the console or exiting the process. + +The command surface (`press`, `keyboard`, `inputKeycode`, `handleSettings`, `connect` / `disconnect`) carries over from 1.5.x. What changes is how the package is loaded, how it reports, and how initialization behaves. + +--- + +## 💥 Breaking Changes + +### ES module package with an `exports` map + +`package.json` now sets `"type": "module"` and an `exports` map (`import` → `index.mjs`, `require` → `index.cjs`) in place of `"main": "src/lib/android-tv-remote.js"`. Source files were renamed from `.js` to `.mjs`. + +- `import createRemote from "@cldmv/node-android-tv-remote"` and `require("@cldmv/node-android-tv-remote")` both return the `createRemote` factory. +- Deep imports of internal files (for example `.../src/lib/android-tv-remote.js`) no longer resolve; import from the package root. +- The CommonJS entry loads `index.mjs` through `require()`, which only works on Node.js versions with `require(esm)` (^20.19.0 or >=22.12.0). On older Node.js, load the package with `import()`. (v2.1.7 later made this fail with a clear message.) + +### Console output replaced by `log` and `error` events + +The library no longer writes to the console or calls `process.exit()`. Each remote exposes `on`, `off` and `once` (all chainable) and `emit`, and reports through two events: + +- `log` — `{ level, message, source, timestamp, data? }`, with `level` one of `info`, `warn`, `error` or `debug`. Suppressed when `quiet` is true (the default), apart from errors. +- `error` — `{ error, source, message, timestamp }`. + +The emitter is a Node.js `EventEmitter`, so an `error` event with no listener attached throws. Attach an `error` listener on every remote: + +```js +import createRemote from "@cldmv/node-android-tv-remote"; + +const remote = createRemote({ ip: "192.168.1.100" }); +remote.on("log", (e) => console.log(`[${e.level}] ${e.source}: ${e.message}`)); +remote.on("error", (e) => console.error(e.source, e.error.message)); + +await remote.press.home(); +``` + +### `autoConnect: false` no longer connects at creation + +In 1.5.5, `createRemote()` started connecting at creation even with `autoConnect: false`. Initialization now honours the option: with `autoConnect: false`, `initPromise` resolves immediately and nothing connects until `connect()` is called. + +--- + +## ✨ Features + +- **`createAndroidTVRemote(config)`** — an async factory that resolves once initialization has finished; also available as `createRemote.create`. Exported by name from the ESM entry. +- **`connectTimeout`** option (default `10000` ms) bounds each ADB connection attempt, and initialization times out instead of hanging when a device never answers. +- **`AndroidTVSetup`** is exported from the package entry (ESM and CommonJS) for device-setup scripting. +- Clearer onboarding messages, sent as `log` events, for unauthorized devices and refused connections. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v2/v2.0.0.md](./v2.0.0.md) — this changelog. +- README — new key-features list, ESM and CommonJS usage, an event-driven usage guide, the event data shapes, and lists of the main methods, event methods and properties. + +--- + +## 🔧 Dependencies + +- `adbkit` 2.11.1 → `@devicefarmer/adbkit` ^3.3.8 (maintained fork, same ADB protocol). + +--- + +## Upgrade notes + +1. Load the package from its root with `import` (any supported Node.js) or `require` (Node.js ^20.19.0 or >=22.12.0); remove deep imports of internal files. +2. Attach an `error` listener to every remote, and a `log` listener if you relied on the console output. +3. If you passed `autoConnect: false`, call `connect()` yourself before sending commands (the commands still auto-connect only when `autoConnect` is true). +4. Optionally switch to `await createAndroidTVRemote(config)` to get a remote that has finished initializing. diff --git a/docs/changelog/v2/v2.1.0.md b/docs/changelog/v2/v2.1.0.md new file mode 100644 index 0000000..54b12de --- /dev/null +++ b/docs/changelog/v2/v2.1.0.md @@ -0,0 +1,99 @@ +# @cldmv/node-android-tv-remote v2.1.0 Changelog + +**Release Date**: October 2025 +**Release Type**: Minor + +--- + +## Overview + +Version 2.1.0 adds screen capture, device power and boot management, and a reworked keyboard API. Screenshots come back as PNG streams (optionally resized through `sharp`) or are saved to disk, and new `ensureAwake()`, `reboot()` and `waitBootComplete()` methods manage the device's power state. + +The release also changes the API shape in ways that break existing callers despite the minor version number: `createRemote()` is now async, `handleSettings()` became `setSettings()`, `keyboard.key` was cut down to typeable characters only, and `AndroidTVSetup` is no longer exported. Each is covered under **Breaking Changes** below. + +--- + +## ✨ Features + +### Screen capture: `screencap()`, `thumbnail()` and `lastScreencapData` + +- `screencap({ width?, height?, filepath? })` captures the screen. Without `filepath` it resolves to a PNG stream; with `filepath` it saves the image in the background and resolves when the save has been started. `width` / `height` resize through `sharp`; without them the ADB PNG stream is piped straight through, skipping image processing. +- `thumbnail({ width = 240, height?, filepath? })` calls `screencap()` with a 240 px default width. +- `lastScreencapData` holds the most recent processed screenshot stream. +- Concurrent calls each get their own ADB stream, and progress is reported through `screencap-start`, `screencap-captured`, `screencap-processing`, `screencap-ready`, `screencap-saved` and `screencap-complete` events. + +### Power and boot management + +- `ensureAwake()` checks the device's power state, wakes it if needed and resolves `true` when it is ready. +- `reboot()` reboots the device through ADB's native reboot. +- `waitBootComplete(timeout = 60000)` resolves `true` once the device has finished booting. +- New `scripts/wake-tv.mjs` utility. + +### Keyboard character map + +`keyboard.key` is now built from a new `src/data/keyboard-keys.json` map of 71 typeable characters (letters, digits, symbols and control characters), and `keyboard.key.shift` holds shifted variants only for the 47 keys whose output actually changes with shift. `.keycode()` methods are created only where a keycode exists. + +--- + +## 💥 Breaking Changes + +All of the following shipped in a minor release. + +### `createRemote()` returns a Promise + +`createRemote()` is now an `async` function that resolves to the remote once it is initialized, so `createAndroidTVRemote()` becomes a plain alias. Code that used the return value directly breaks: + +```js +// 2.0.x +const remote = createRemote({ ip: "192.168.1.100" }); + +// 2.1.0 +const remote = await createRemote({ ip: "192.168.1.100" }); +``` + +This applies to the CommonJS entry too: `require("@cldmv/node-android-tv-remote")` returns the async factory. + +### `handleSettings(mode, overrideQuiet)` renamed to `setSettings(mode = "set")` + +`handleSettings` is gone. Call `remote.setSettings()` to apply the recommended settings, or `remote.setSettings("get")` to read the current values. + +### `keyboard.key` holds typeable characters only + +In 2.0.x, `keyboard.key` exposed every Android keycode (273 keys, including remote buttons such as `home`, `back`, `power` and the D-pad), each with a `shift` variant and a `.keycode()` method. In 2.1.0 it holds only the 71 typeable characters, `keyboard.key.shift` only the 47 characters that change with shift, and shifted keys no longer have `.keycode()` methods (ADB can't send a keycode combination). Move remote-button calls to `remote.press.()`, and send shifted characters through `keyboard.key.shift.()` or `keyboard.text()`. + +### `AndroidTVSetup` is no longer exported + +The `AndroidTVSetup` helper and its source file (`src/lib/adb/setup.mjs`) were removed, along with its named export from both entries. + +### New native dependency and Node.js floor + +`sharp` ^0.34.4 is now a runtime dependency, so installation pulls a platform-specific native binary. `package.json` declares `engines.node` `^18.17.0 || ^20.3.0 || >=21.0.0`, and the package now publishes only `src/`, `scripts/`, the two entry files and the README (`files`), so the `test/` and `docs/` folders no longer ship in the npm package. + +--- + +## 🐛 Bug Fixes + +- Remote-control buttons no longer appear in the `keyboard` namespace, and invalid shift-keycode methods are no longer generated. + +--- + +## 📚 Documentation + +- **NEW:** [docs/changelog/v2/v2.1.0.md](./v2.1.0.md) — this changelog. +- README — documents screen capture, device management (`ensureAwake`, `reboot`, `waitBootComplete`), async initialization and the keyboard model. + +--- + +## 🔧 Dependencies + +- Added `sharp` ^0.34.4 (runtime) for screenshot resizing. + +--- + +## Upgrade notes + +1. `await` every `createRemote()` call (ESM and CommonJS). +2. Replace `handleSettings(mode)` with `setSettings(mode)`; the `"get"` mode is unchanged, and `"set"` is the default. +3. Move remote-button calls from `keyboard.key.