tplink-tapo
v0.0.2
Published
Zod-first Tapo discovery and control hub (cloud + LAN, KLAP-aware, TPAP-explicit)
Maintainers
Readme
tplink-tapo
Zod-first Tapo discovery + control hub for Bun/TypeScript: cloud inventory, ARP IP merge, and first-party local KLAP (handshake + AES session via axios / node:crypto). No runtime dependency on tp-link-tapo-connect.
Current release: 0.0.2
When a plug is on TP-Link’s newer TPAP lock (or third-party local access is off), this package detects and throws TapoTpapBlockedError — it never pretends control succeeded. Open-source stacks implement KLAP/AES only; TPAP RE is out of scope for now.
Built with Bun + Zod v4.
Confirmed working (lab)
Private lab, 2026-08-09/10. Prefer discovery encrypt_type over firmware guessing — not every 1.4.x unit is TPAP.
| Lab name | Model | FW | Local encrypt | Package control |
|----------|-------|-----|---------------|-----------------|
| Bedroom Lamp | P100(AU) | 1.4.6 | KLAP | Yes — native on / off / info (loginKlapByIp, CLI, live tests) |
| HomeNuc | P100(AU) | 1.2.5 class | KLAP (lv 2) | Yes — native KLAP (also verified historically via connect-lib) |
Also working in package (not device-specific): cloud login + device list, ARP table merge (local-devices or arp -a), typed TPAP/403 short-circuit, HTTPS-by-default KLAP sessions with response authentication, bounded requests, and inject hooks for offline tests.
| Not confirmed / blocked | Notes | |-------------------------|--------| | Other P100 1.4.x on TPAP | handshake1 403 until app Third-Party Compatibility restores KLAP | | P110 1.4.0 class | Often same 403/TPAP family — probe per unit | | L530 / colour bulbs | Schemas + actions ready; no live lab row yet | | Cameras, sensors, streaming | Out of scope |
TPAP remediation: enable Third-Party Compatibility in the Tapo app, power-cycle the device, and retry discovery/control.
Status
Native KLAP / cloud / ARP path — layouts, schemas, first-party transports, offline unit tests + opt-in live hardware tests.
| Area | State |
|------|--------|
| Zod schemas (Device, Action, credentials, discovery) | Done |
| Cloud list + ARP merge | Done (native tplink-cloud + loadArpTable; inject for tests) |
| KLAP control + TPAP/403 errors | Done (native crypto/session, HTTPS default, response validation; inject for tests) |
| Live lab: P100 1.2.5 + P100 1.4.6 KLAP | Confirmed |
| UDP discovery transport | Stub (fixture mapping ready) |
| TPAP reverse-engineering | Out of scope |
| Device inventory persistence | Later |
Install
bun installOptional: local-devices for richer ARP on some platforms (package falls back to parsing arp -a).
Usage
import {
listDevices,
controlByIp,
openDevice,
discoveryRecordToLanDevice,
TapoTpapBlockedError,
} from "tplink-tapo";
const credentials = {
email: process.env.TAPO_EMAIL!,
password: process.env.TAPO_PASSWORD!,
};
// Cloud inventory (inject cloudList in tests)
const devices = await listDevices(credentials, {
cloudList: async () => rawCloudArray,
arp: [{ ip: "192.168.1.10", mac: "aa:bb:cc:dd:ee:ff" }],
});
// Local control — fails with TapoTpapBlockedError on TPAP / handshake 403
// HTTPS is the default. Legacy HTTP-only devices require:
// { klapOptions: { protocol: "http" } } on the control call.
try {
await controlByIp(credentials, "192.168.1.42", { type: "on" });
} catch (e) {
if (e instanceof TapoTpapBlockedError) {
console.error("Unsupported local protocol", e.ip);
}
throw e;
}
const handle = openDevice(devices[0]!, { credentials });
await handle.toggle();CLI (smoke)
cp .env.example .env # set TAPO_EMAIL / TAPO_PASSWORD
bun run src/cli.ts list
bun run src/cli.ts on --ip 192.168.1.50
bun run src/cli.ts info --ip 192.168.1.50Scripts
| Command | Purpose |
|---------|---------|
| bun test | Unit tests (fixtures, no network) |
| bun run typecheck | tsc --noEmit |
| bun run build | Bundle library + .d.ts into dist/ (no CLI) |
| bun run cli | Smoke CLI via source (src/cli.ts) |
| bun run scripts/klap-audit.ts | One-handshake local KLAP security audit |
| bun run clean | Remove dist/ |
Layout
src/
schemas/ Zod contracts
cloud/ Native cloud login/list + listDevices facade
discovery/ LAN UDP discovery (+ fixture mapping)
local/ Native KLAP crypto/session + control + TPAP gates
devices/ DeviceHandle facade
util/ MAC, nickname, merge, ARP
errors.ts Typed error hierarchy
cli.ts Smoke CLI
index.ts Public exports
test/
fixtures/ Cloud list + KLAP/TPAP discovery samples
unit/ Offline unit tests
live/ Opt-in hardware tests (TAPO_LIVE_TESTS=1)Design rules
- KLAP first for local control; do not claim TPAP support until reverse-engineered.
- Detect TPAP early (
mgt_encrypt_schm.encrypt_type, HTTP403on/app/handshake*). - Typed errors —
TapoTpapBlockedErroris distinct from auth/network failures. - Stable identity — prefer cloud
deviceId, match LAN via normalized MAC. - Inject transports in tests; keep unit suite offline. Default runtime path is native (no peer required).
- Secure transport by default — use HTTPS and validate KLAP response signatures; opt into HTTP only for legacy isolated-LAN devices.
Next (P1)
- UDP discovery implementation (or thin
python-kasabridge) - Controllability probe helper (
probeLocal(ip)→ handshake status) - AES legacy securePassthrough fallback for older devices
- Colour named-map polish for bulbs
