Skip to content

Commit

Permalink
Breaking: Rewrite to remove legacy stuff (#55)
Browse files Browse the repository at this point in the history
* Breaking: Removed legacy interface and checks

- The module now only exports the lookup function.
- Removed the IP and service name checks. The API is already doing
that.
- The `ip` now defaults to `me` which is your current public IP.
- Updated tests, docs and example to reflect the changes.
  • Loading branch information
fvdm committed Mar 31, 2024
1 parent 64fea4e commit ed46b4d
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 845 deletions.
523 changes: 2 additions & 521 deletions CHANGELOG.md

Large diffs are not rendered by default.

102 changes: 28 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,29 @@
# geoip2ws

Unofficial Node.js module for the Maxmind GeoIP2
Web Services.
Unofficial Node.js module for the Maxmind GeoIP2 Web Services.

[![npm](https://img.shields.io/npm/v/geoip2ws.svg?maxAge=3600)](https://www.npmjs.com/package/geoip2ws?activeTab=versions)
[![Build Status](https://github.com/fvdm/nodejs-geoip2ws/actions/workflows/node.js.yml/badge.svg?branch=master)](https://github.com/fvdm/nodejs-geoip2ws/actions/workflows/node.js.yml)
[![Coverage Status](https://coveralls.io/repos/github/fvdm/nodejs-geoip2ws/badge.svg?branch=master)](https://coveralls.io/github/fvdm/nodejs-geoip2ws?branch=master)

* [Changelog](https://github.com/fvdm/nodejs-geoip2ws/blob/master/CHANGELOG.md)
* [Changelog](https://github.com/fvdm/nodejs-geoip2ws/releases)
* [Node.js](https://nodejs.org)
* [Maxmind GeoIP2 Web Services](https://www.maxmind.com/en/geoip2-precision-services)
* [API documentation](https://dev.maxmind.com/geoip/docs/web-services)


## Usage

You can provide the configuration in the require function
or inline at lookup time. The lookup always returns a Promise.
The configuration parameters must be wrapped in an object.
This allows the use of spread `{ ...config, ip: '1.2.3.4' }`
if you want to keep the main config in a variable.
The lookup always returns a Promise.

- [Response examples](https://dev.maxmind.com/geoip/docs/web-services/responses?lang=en#bodies)
- [List of API errors](https://dev.maxmind.com/geoip/docs/web-services/responses?lang=en#errors)


### Normal configuration

```js
const geo = require( 'geoip2ws' )( {
userId: '12345',
licenseKey: 'abc678',
} );

geo( {
service: 'city',
ip: '1.2.3.4',
} )
.then( console.log )
.catch( console.error )
;
```


### Inline configuration

```js
const geo = require( 'geoip2ws' )();
const geo = require( 'geoip2ws' );

geo( {
userId: '12345',
Expand All @@ -66,70 +46,44 @@ You can find both [*here*](https://www.maxmind.com/en/accounts/current/license-k
`npm i geoip2ws`


## The functions
## Configuration

### Setup

The _first function_ is the global config and returns the
lookup function. It takes these settings:

parameter | type | default | description
:----------------|:--------|:--------------------------|:-----------
[userId] | string | | User ID
[licenseKey] | string | | License key
[service] | string | city | `insights`, `country` or `city`
[endpoint] | string | https://geoip.maxmind.com | Override endpoint hostname or url
[timeout] | integer | 5000 | Request timeout in ms
parameter | type | default | description
:----------|:-------|:--------------------------|:-----------
userId | string | | User ID
licenseKey | string | | License key
[ip] | string | me | The IP address or 'me' for your current IP
[service] | string | city | `insights`, `country` or `city`
[endpoint] | string | https://geoip.maxmind.com | Override endpoint url, include the protocol prefix
[timeout] | number | 5000 | Request timeout in ms

```js
const geo = require( 'geoip2ws' )( {
{
ip: '2a02:abc:def::123',
userId: '1234',
licenseKey: 'abc',
service: 'country',
timeout: 2000,
} );
}
```

If you are providing the details in the lookup
function, then you don't have to set them here but
you do need to run this setup function once.

```js
const geo = require( 'geoip2ws' )();
```


### Lookup

The _second function_ does the IP-address lookup and
takes `ip` and optionally the same settings as above.
### Endpoint

parameter | type | description
:----------|:---------|:-----------
ip | string | The IPv4 or IPv6 address to lookup
You can change the `endpoint` to use a http proxy or target a specific datacenter.
Make sure to prefix the hostname with the protocol, i.e. `https://`.

```js
geo( {
ip: '1.2.3.4',
service: 'city',
endpoint: 'geoip-eu-west.maxmind.com',
} )
.then( processData )
.catch( console.error )
;
```
For the GeoLite2 web service set the `endpoint` to `https://geolite.info`.


#### Errors
## Errors

error message | description
:---------------|:-----------
invalid service | The service name is invalid
invalid ip | The IP-address is invalid
Errors from the request or API are thrown and can be caught in the Promise.

These two conditions are checked before sending the request.
Meaning it won't cost you account credits.
For API errors the message is prefixed with `API: ` string.

- [List of API errors](https://dev.maxmind.com/geoip/docs/web-services/responses?lang=en#errors)


## Unlicense

Expand Down
14 changes: 7 additions & 7 deletions example.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-disable no-console */
/* eslint-disable no-console, key-spacing */
const geoip = require( 'geoip2ws' );

const geoip = require( 'geoip2ws' )( {
userId: '00000',
geoip( {
userId: '00000',
licenseKey: 'abc123',
service: 'city',
} );

geoip( { ip: 'me' } )
service: 'city',
ip: 'me',
} )
.then( data => {
console.dir( data, {
depth: null,
Expand Down
136 changes: 34 additions & 102 deletions geoip2ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,136 +7,68 @@ Feedback: https://github.com/fvdm/nodejs-geoip2ws/issues
License: Unlicense (public domain, see LICENSE file)
*/

const { isIP } = require( 'net' );

// settings store
let config = {};


/**
* Process response body
*
* @param {object} res doRequest() response
*
* @return {Promise<object>}
*/

async function doResponse ( res ) {
const data = await res.json();

if ( data.error ) {
const error = new Error( data.error );

error.code = data.code;
throw error;
}

// Fix API inconsistencies
if ( Array.isArray( data.subdivisions ) && data.subdivisions.length ) {
data.most_specific_subdivision = data.subdivisions[data.subdivisions.length - 1];
}
else {
data.subdivisions = [];
}

if ( ! data.most_specific_subdivision ) {
data.most_specific_subdivision = {};
}

return data;
}


/**
* Perform lookup
* GeoIP lookup
*
* @param {object} o
*
* @param {string} o.ip IP-address, hostname or 'me' to look up
* @param {string} [o.userId] Account user ID
* @param {string} [o.licenseKey] Account license key
* @param {string} [o.service] Account service name
* @param {string} [o.endpoint] API hostname or url
* @param {number} [o.timeout] Request time out in milliseconds
* @param {string} o.userId Account user ID
* @param {string} o.licenseKey Account license key
* @param {string} [o.ip='me'] IP-address, hostname or 'me' to look up
* @param {string} [o.service='city'] Account service name
* @param {string} [o.endpoint] API hostname or url
* @param {number} [o.timeout=5000] Request time out in milliseconds
*
* @return {Promise<object>}
*/

async function doLookup ( {
module.exports = async function geoip2ws ( {

ip,
userId = config.userId,
licenseKey = config.licenseKey,
service = config.service,
endpoint = config.endpoint,
timeout = config.timeout,
userId,
licenseKey,
ip = 'me',
service = 'city',
endpoint = 'https://geoip.maxmind.com',
timeout = 5000,

} ) {

// check input
if ( ! /^(country|city|insights)$/.test( service ) ) {
throw new Error( 'invalid service' );
}

if ( ip !== 'me' && ! isIP( ip ) ) {
throw new Error( 'invalid ip' );
}

// build url
endpoint = endpoint.replace( /\/$/, '' );
endpoint += `/geoip/v2.1/${service}/${ip}`;

if ( ! endpoint.match( /^https?\:\/\// ) ) {
endpoint = `https://${endpoint}`;
}

// request
const res = await fetch( endpoint, {
signal: AbortSignal.timeout( timeout ),
headers: {
'Accept': `application/vnd.maxmind.com-${service}+json; charset=UTF-8; version=2.1`,
'Accept': 'application/json',
'Accept-Charset': 'UTF-8',
'Authorization': 'Basic ' + Buffer.from( `${userId}:${licenseKey}` ).toString( 'base64' ),
'User-Agent': 'geoip2ws.js (https://github.com/fvdm/nodejs-geoip2ws)',
'User-Agent': 'fvdm/nodejs-geoip2ws',
},
} );

return doResponse( res );

}

const data = await res.json();

/**
* Module interface
*
* @param {object} o
*
* @param {string} [o.userId] Account user ID
* @param {string} [o.licenseKey] Account license key
* @param {string} [o.service] Account service name
* @param {string} [o.endpoint] API hostname or url
* @param {number} [o.timeout] Request time out in milliseconds
*
* @return {function} doLookup
*/
// Process API error
if ( data.error ) {
const error = new Error( `API: ${data.error}` );

module.exports = function setup ( {
error.reason = data.code;
throw error;
}

userId = null,
licenseKey = null,
service = 'city',
endpoint = 'https://geoip.maxmind.com',
timeout = 5000,
// Fix response inconsistencies
data.most_specific_subdivision = {};

} = {} ) {
if ( ! Array.isArray( data.subdivisions ) ) {
data.subdivisions = [];
}

config = {
userId,
licenseKey,
service,
endpoint,
timeout,
};
if ( data.subdivisions.length ) {
data.most_specific_subdivision = data.subdivisions[data.subdivisions.length - 1];
}

return doLookup;
// All good
return data;

};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"name": "geoip2ws",
"description": "Maxmind GeoIP2 Web Services (unofficial) - IP and hostname geolocation",
"version": "2.1.0",
"version": "3.0.0",
"repository": {
"type": "git",
"url": "git://github.com/fvdm/nodejs-geoip2ws.git"
Expand Down
Loading

0 comments on commit ed46b4d

Please sign in to comment.