-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathipinfoLiteWrapper.ts
More file actions
167 lines (148 loc) · 5.39 KB
/
Copy pathipinfoLiteWrapper.ts
File metadata and controls
167 lines (148 loc) · 5.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import fetch from "node-fetch";
import type { RequestInit, Response } from "node-fetch";
import { defaultEuCountries } from "../config/utils";
import Cache from "./cache/cache";
import LruCache from "./cache/lruCache";
import ApiLimitError from "./errors/apiLimitError";
import { isInSubnet } from "subnet-check";
import {
REQUEST_TIMEOUT_DEFAULT,
CACHE_VSN,
HOST_LITE,
BOGON_NETWORKS,
IPinfoLite,
IPBogon
} from "./common";
import VERSION from "./version";
const clientUserAgent = `IPinfoClient/nodejs/${VERSION}`;
export default class IPinfoLiteWrapper {
private token: string;
private baseUrl: string;
private euCountries: Array<string>;
private cache: Cache;
private timeout: number;
/**
* Creates IPinfoWrapper object to communicate with the [IPinfo](https://ipinfo.io/) API.
*
* @param token Token string provided by IPinfo for registered user.
* @param cache An implementation of IPCache interface. If it is not provided
* then LruCache is used as default.
* @param timeout Timeout in milliseconds that controls the timeout of requests.
* It defaults to 5000 i.e. 5 seconds. A timeout of 0 disables the timeout feature.
* @param i18nData Internationalization data for customizing countries-related information.
* @param i18nData.countries Custom countries data. If not provided, default countries data will be used.
* @param i18nData.countriesFlags Custom countries flags data. If not provided, default countries flags data will be used.
* @param i18nData.countriesCurrencies Custom countries currencies data. If not provided, default countries currencies data will be used.
* @param i18nData.continents Custom continents data. If not provided, default continents data will be used.
* @param i18nData.euCountries Custom EU countries data. If not provided or an empty array, default EU countries data will be used.
*/
constructor(
token: string,
cache?: Cache,
timeout?: number,
i18nData?: {
euCountries?: Array<string>;
},
baseUrl?: string
) {
this.token = token;
this.euCountries =
i18nData?.euCountries && i18nData?.euCountries.length !== 0
? i18nData.euCountries
: defaultEuCountries;
this.cache = cache ? cache : new LruCache();
this.timeout =
timeout === null || timeout === undefined
? REQUEST_TIMEOUT_DEFAULT
: timeout;
this.baseUrl = baseUrl || `https://${HOST_LITE}`;
}
public static cacheKey(k: string) {
return `${k}:${CACHE_VSN}`;
}
public async fetchApi(
path: string,
init: RequestInit = {}
): Promise<Response> {
const headers = {
Accept: "application/json",
Authorization: `Bearer ${this.token}`,
"Content-Type": "application/json",
"User-Agent": clientUserAgent
};
const request = Object.assign(
{
timeout: this.timeout,
method: "GET",
compress: false
},
init,
{ headers: Object.assign(headers, init.headers) }
);
const url = [this.baseUrl, path].join(
!this.baseUrl.endsWith("/") && !path.startsWith("/") ? "/" : ""
);
return fetch(url, request).then((response: Response) => {
if (response.status === 429) {
throw new ApiLimitError();
}
if (response.status >= 400) {
throw new Error(
`Received an error from the IPinfo API ` +
`(using authorization ${headers["Authorization"]}) ` +
`${response.status} ${response.statusText} ${response.url}`
);
}
return response;
});
}
/**
* Lookup IP information using the IP.
*
* @param ip IP address against which the location information is required.
* @return Response containing location information.
*/
public async lookupIp(
ip: string | undefined = undefined
): Promise<IPinfoLite | IPBogon> {
if (ip && this.isBogon(ip)) {
return {
ip,
bogon: true
};
}
if (!ip) {
ip = "me";
}
const data = await this.cache.get(IPinfoLiteWrapper.cacheKey(ip));
if (data) {
return data;
}
return this.fetchApi(ip).then(async (response) => {
const data = await response.json();
const ipinfo = {
ip: data.ip,
asn: data.asn,
asName: data.as_name,
asDomain: data.as_domain,
countryCode: data.country_code,
country: data.country,
continentCode: data.continent_code,
continent: data.continent,
isEU: this.euCountries.includes(data.country_code)
};
this.cache.set(IPinfoLiteWrapper.cacheKey(ip), ipinfo);
return ipinfo;
});
}
private isBogon(ip: string): boolean {
if (ip != "") {
for (var network of BOGON_NETWORKS) {
if (isInSubnet(ip, network)) {
return true;
}
}
}
return false;
}
}