## Nodejs package to communicate with Netgear routers via its SOAP interface.
Can do more than the [NETGEAR Nighthawk app](https://www.netgear.com/home/services/nighthawk-app/) or [NETGEAR Orbi app](https://www.netgear.com/home/services/orbi-app/) can do.

## Supported routers
In general: If you can use the NETGEAR Nighthawk app or the NETGEAR Orbi app to manage your router, this node package will most likely work. On older routers that predate those apps, the same is true of the legacy NETGEAR genie app. Some functionality, like blocking/unblocking an attached device, only works on certain router types. MAKE SURE YOU ARE ON THE LATEST ROUTER FIRMWARE!

You can check your router version by browsing to [routerlogin.net](http://routerlogin.net/currentsetting.htm). According to the genie and NETGEAR Nighthawk app descriptions, at least the following routers or extenders should work:

Nighthawk: AX8 AX12 Tri-Band AX12 XR300 XR450 XR500 XR700 AC2100 AC2400 AC2600 R9000 R8900 R8500 R8300 R8000 R8000P R7900P R7960P R7900 R7800 R7000P R7000 R6900P R6900v2 R6900 R6850 R6800 R7450 R6700v3 R6700v2 R6400v2 R6400 R6350 R6260 R6230 R6220 R6120 R6080 R6020

Nighthawk Extenders: EX7700

Other Wi-Fi Routers: Orbi AC1450 Centria (WNDR4700, WND4720) JNR1010 JNR3210 JR6150 JWNR2010 R6050 R6100 R6200  R6250 R6300 R7500 WNDR3400v2 WNDR3700v3 WNDR3800 WNDR4000 WNDR4300 WNDR4500 WNDRMAC WNR1000v2 WNR1500 WNR2020 WNR2020v2 WNR2000v3 WNR2200 WNR2500 WNR3500Lv2 WNR612v2 WNR614

DSL Modem Gateways: DGN2200B DGND3700B D3600 D6000 D6100 D6200 D6000 D6200B D6300 D6300B D6400 D7000 D7800 DGN1000 DGN2200v3 DGN2200v4 DGND3700v2 DGND3800B DGND4000

Cable Gateway: C7000 C6300 C6250 C3700 C3000 N450

### Newer / untested routers
Netgear doesn't publish a list of which models expose this SOAP interface, so the list above is built from years of community reports rather than official documentation - it predates most WiFi 6/6E/7-generation routers. The interface does appear to still exist on at least some current-generation hardware: a [2023 security disclosure](https://therecord.media/netgear-releases-patches-for-two-bugs) documents a SOAP-API authentication flaw specifically in the WiFi 6E **Orbi 760** series, confirming that line still runs a SOAP API. Whether login and the various get/set calls in this package still work unmodified on Orbi 760+/WiFi 7-generation Orbi, or on recent Nighthawk WiFi 6/6E models, has not been verified by this package - if you have one of these and can confirm it works (or doesn't), please [open an issue](https://github.com/gruijter/netgear.js/issues).

Recent firmware on some of these models is HTTPS-only: it refuses plain `http` on port 80 outright, and serves both `currentsetting.htm` and the SOAP endpoint over TLS on port 443, 5043 or 5555. Since v5.1.0 autodiscovery handles this - `getCurrentSetting()` falls back to those TLS ports when :80 cannot be reached, and `login()` adopts the discovered port and scheme - so you should not need to set `port` or `tls` by hand.

Some routers go a step further and serve no reachable `currentsetting.htm` at all, while still answering SOAP normally. Since v5.2.0 those are found too, by probing the SOAP endpoints themselves: `login()` does this as a last resort when no `port` is set, and `discover()` scans the LAN gateway addresses this way once both `currentsetting.htm` passes come up empty. No credentials are needed for it - the probe just checks whether a SOAP endpoint answers at all. The result of such a discovery necessarily carries only `host`, `port` and `tls`; every other `currentSetting` property is read out of `currentsetting.htm` and is simply absent.

If discovery still fails on such a router, run the compatibility test (`npm test password=mySecretPassword`) and include its output in an issue: the `effective transport:` line reports which port and scheme were actually used.

## Requirements
Node.js >= 22.

## Installation:
```
> npm i netgear
```

## Test from CLI after installation:
```
> cd node_modules/netgear
> npm test password=mySecretPassword
```
Note: The automated unit test suite (mocked router, no hardware needed) is a repo-only dev tool, not part of the published package - `npm test -- --unit` only works from a git clone of this repo, not from an installed copy:
```
> git clone https://github.com/gruijter/netgear.js.git
> cd netgear.js
> npm i
> npm test -- --unit
```

## Quickstart:
```js
// create a router session, login to router, fetch attached devices
const Netgear = require('netgear');

const router = new Netgear();

async function getDevices() {
	try {
		const options = { password: 'mySecretPassword' };
		await router.login(options);
		const deviceArray = await router.getAttachedDevices();
		console.log(deviceArray);
	} catch (error) {
		console.log(error);
	}
}

getDevices();
```

## Documentation:
[Detailed documentation](https://gruijter.github.io/netgear.js/ "Netgear.js documentation")

## Migrating from version 4
Version 5 is methodwise fully compatible with version 4 - no code changes needed beyond bumping your Node.js version to >= 22. The following methods were added:
- `getWPASecurityKeys()` / `get5GWPASecurityKeys()` / `get5G1WPASecurityKeys()` - the WPA passphrase for the 2.4GHz / 5GHz-2 / 5GHz-1 (tri-band routers) network.
- `getAllMACAddresses()` - all known parental-control MAC addresses.
- `getAllSatellites()` - list of Orbi mesh satellites. Still experimental/unverified - the R8000 above isn't an Orbi, so no real capture confirms this one.

NetgearRouter also now extends Node's `EventEmitter` and emits a `'log'` event - see [Logging](#logging) below. This is purely additive; nothing changes if you don't subscribe to it.

## Logging
Logging is purely additive on top of the existing error handling - every method still rejects/throws exactly as before (see the try/catch examples throughout this README), and you still need to handle those. The `'log'` event is a separate, supplementary diagnostic channel for detail that doesn't fit in a thrown Error's message (e.g. full request/response tracing) - it never replaces or changes what gets thrown.

`NetgearRouter` never writes to the console directly. Instead it emits a `'log'` event so you can route output into whatever logger your app already uses:
```js
const router = new Netgear({ password: 'mySecretPassword', logLevel: 'warn' });

router.on('log', ({ level, message, ...context }) => {
	console.log(`[${level}] ${message}`, context);
});
```
`logLevel` (default `'warn'`) controls verbosity: `'silent' < 'error' < 'warn' < 'info' < 'debug'`. At the default `'warn'` level you only see genuine problems (a failed login, a failed router discovery) - fallback ladders (e.g. trying login method 2 before falling back to method 1) don't spam a warning for the expected first failure. Set `logLevel: 'debug'` (or `router.logLevel = 'debug'` at any time) to get full SOAP request/response tracing, including timing and a truncated response body. Session cookies and the login password are always redacted before anything is logged, even at `'debug'`.

## Example code:
```js
const NetgearRouter = require('netgear');

// note: options can be passed in here. See login options.
const router = new NetgearRouter();

// discover a netgear router, including IP address and SOAP port. The discovered address and SOAP port will override previous settings.
// A router that serves no reachable currentsetting.htm is found by probing its SOAP endpoints
// directly, in which case only host, port and tls are reported.
router.discover()
	.then(discovered => console.log(discovered))
	.catch(error => console.log(error));

// function to get various information
async function getRouterInfo() {
	try {
		// Get router type, soap version, firmware version and internet connection status without login.
		// Autodiscovers the SOAP port and whether TLS is needed. Optional extra arguments:
		// getCurrentSetting(host, timeout, { httpsFallback: true }) - httpsFallback (default
		// true) also probes currentsetting.htm over TLS on ports 443, 5043 and 5555 when plain
		// http:80 cannot be reached, which is what HTTPS-only firmware needs.
		const currentSetting = await router.getCurrentSetting();
		console.log(currentSetting);

		// for other methods you first need to be logged in. Passing options will override previous settings
		const options = {
			password: 'mySecretPassword',	// Password can also be passed during login
			host: '192.168.1.1',	// Autodiscovery will be performed when left out
			port: 80,	// SOAP port. Autodiscovery will be performed when left out
			tls: false,	// Use HTTPS for the SOAP calls. Autodiscovery will be performed when left out
		}
		await router.login(options);

		// Get router type, serial number, hardware version, firmware version, soap version, firewall version, etc.
		const info = await router.getInfo();
		console.log(info);

		// Get the support features.
		const supportFeatures = await router.getSupportFeatureListXML();
		console.log(supportFeatures);

		// Get the parental control status.
		const parentalControlEnabled = await router.getParentalControlEnableStatus();
		console.log(`Parental Controls enabled: ${parentalControlEnabled}`);

		// Get the qosEnableStatus.
		const qosEnabled = await router.getQoSEnableStatus();
		console.log(`Qos Enabled: ${qosEnabled}`);

		// Get the BlockDeviceEnabled Status (= device access control)
		const accessControlEnabled = await router.getBlockDeviceEnableStatus();
		console.log(`Device Access Control enabled: ${accessControlEnabled}`);

		// get a list of attached devices
		const attachedDevices = await router.getAttachedDevices();
		console.log(attachedDevices);

		// get the trafficMeterEnabled status
		const trafficMeterEnabled = await router.getTrafficMeterEnabled();
		console.log(`Traffic Meter Enabled: ${trafficMeterEnabled}`);

		// get the trafficMeterOptions
		const trafficMeterOptions = await router.getTrafficMeterOptions();
		console.log(trafficMeterOptions);

		// get traffic statistics for this day and this month. Note: traffic monitoring must be enabled in router
		const traffic = await router.getTrafficMeter();
		console.log(traffic);

		// check for new router firmware and release note
		const firmware = await router.checkNewFirmware();
		console.log(firmware);

		// logout
		console.log('going to logout now');
		await router.logout();

	}	catch (error) {
		console.log(error);
	}
}

getRouterInfo();


// function to block or allow an attached device
async function blockOrAllow(mac, action) {
	try {
		await router.login();
		await router.setBlockDeviceEnable(true);
		const success = await router.setBlockDevice(mac, action);
		console.log(success);
	}	catch (error) {
		console.log(error);
	}
}

// block a device with mac 'AA:BB:CC:DD:EE:FF'
blockOrAllow('AA:BB:CC:DD:EE:FF', 'Block');

// allow a device with mac 'AA:BB:CC:DD:EE:FF'
blockOrAllow('AA:BB:CC:DD:EE:FF', 'Allow');


// function to retrieve Guest Wifi status
async function getGuestWifiStatus() {
	try {
		await router.login();
		const guestWifiEnabled = await router.getGuestWifiEnabled();
		console.log(`2.4G-1 Guest wifi enabled: ${guestWifiEnabled}`);
		const guestWifi5GEnabled = await router.get5GGuestWifiEnabled();
		console.log(`5G-1 Guest wifi enabled: ${guestWifi5GEnabled}`);
		const guestWifi5G2Enabled = await router.get5GGuestWifi2Enabled();
		console.log(`5G-2 Guest wifi enabled: ${guestWifi5G2Enabled}`);
	} catch (error) {
		console.log(error);
	}
}

getGuestWifiStatus();


// function to enable/disable wifi
async function doWifiStuff() {
	try {
		await router.login();
		// enable 2.4GHz-1 guest wifi
		await router.setGuestWifi(true);
		console.log('2.4-1 enabled');
		// disable 5GHz-1 guest wifi
		await router.set5GGuestWifi(false);
		console.log('5-1 disabled');
		// disable 5GHz-2 guest wifi
		await router.set5GGuestWifi2(false);
		console.log('5-2 disabled');
		// set 5GHz-1 wifi to channel 40
		await router.setWifiChannel(40, '5G');
	}	catch (error) {
		console.log(error);
	}
}

doWifiStuff();


// function to enable/disable QOS
async function doQosStuff() {
	try {
		await router.login();
		// Set the qosEnableStatus.
		await router.setQoSEnableStatus(true);
		console.log('Qos enabled');
		// Set the getBandwidthControlOptions.
		console.log('trying to set Qos Bandwidth options...');
		await router.setBandwidthControlOptions(60.5, 50.5);	// in MB/s
		// Get the getBandwidthControlOptions.
		console.log('trying to get Qos Bandwidth options...');
		const bandwidthControlOptions = await router.getBandwidthControlOptions();
		console.log(bandwidthControlOptions);
	}	catch (error) {
		console.log(error);
	}
}

doQosStuff();


// function to enable/disable TrafficMeter
async function doTrafficMeterStuff() {
	try {
		await router.login();
		// enable trafficMeter.
		await router.enableTrafficMeter(true);
		console.log('Traffic meter enabled');
	}	catch (error) {
		console.log(error);
	}
}

doTrafficMeterStuff();


// function to enable/disable parental control
async function doParentalControlStuff() {
	try {
		await router.login();
		// disable parental control
		await router.enableParentalControl(false);
		console.log('Parental control disabled');
	}	catch (error) {
		console.log(error);
	}
}

doParentalControlStuff();


// function to change router name
async function setNetgearDeviceName() {
	try {
		await router.login();
		// set router name to 'TEST'
		await router.setNetgearDeviceName('TEST');
		console.log('router name changed to TEST');
	}	catch (error) {
		console.log(error);
	}
}

setNetgearDeviceName();


// function to update router firmware
async function updateNewFirmware() {
	try {
		await router.login();
		console.log('trying to update router firmware');
		await router.updateNewFirmware();
	}	catch (error) {
		console.log(error);
	}
}

updateNewFirmware();


// function to do internet speed test (takes long time!)
async function speedTest() {
	try {
		await router.login();
		console.log('speed test is starting... (wait a minute)')
		const speed = await router.speedTest(); // takes 1 minute to respond!
		console.log(speed);
	}	catch (error) {
		console.log(error);
	}
}

speedTest();


// function to reboot router
async function reboot() {
	try {
		await router.login();
		// Reboot the router
		console.log('going to reboot the router now')
		await router.reboot();
	}	catch (error) {
		console.log(error);
	}
}

reboot();


// function to send WakeOnLan command to a device
async function wol(MAC, secureOnPassword) {
	try {
		console.log(`performing WOL for ${mac}`)
		await router.wol(MAC, secureOnPassword);
	}	catch (error) {
		console.log(error);
	}
}

wol('AA:BB:CC:DD:EE:FF', '00:00:00:00:00:00');
```


