forked from tramseyer/cell-geolocation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrequest.js
205 lines (171 loc) · 6.83 KB
/
request.js
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
const https = require('https');
// Connection timeout, ms
let CONNECTION_TIMEOUT = 3000;
// error messages
const E_NOTFOUND = 'BTS not found';
const E_REQERROR = 'Request error';
// reg expression to fetch coordinates, range and code from OpenCellId response
const RE_FETCH_OPENCELLID_LAT = /\slat="([+\-\d\.]+)"/i;
const RE_FETCH_OPENCELLID_LON = /\slon="([+\-\d\.]+)"/i;
const RE_FETCH_OPENCELLID_RANGE = /\srange="([+\-\d\.]+)"/i;
const RE_FETCH_OPENCELLID_CODE = /\scode="([+\-\d\.]+)"/i;
// error answer in OpenCellId response
const RE_OPENCELLID_ERROR = /err\s+info="[^"]+"\s+code="/i;
// quota exceeded answer for OpenCellId response
const RE_OPENCELLID_QUOTA = /exceeded/i
// quota exceeded answer for UnwiredLabs fallback response
const RE_UNWIREDLABS_QUOTA = /free/i
/**
* Perform request to Location Service.
* Taken from https://github.com/kolonist/bscoords and kept unchaged.
*
* @param {object} options Node.js HTTPS request options.
* @param {*} request_body Request body for POST requests. Can be String or
* Buffer. If you do not need it you can pass null or
* empty string ''.
* @param {*} response_encoding Can be 'utf8' or 'hex' (for Google response).
* @param {*} response_parser Callback function(response) where `response` is
* String with data from Location server. Callback
* function should return object like
* `{lat: 23.12345, lon: 50.12345, range: 1000}` or
* null if there are no coordinates in the answer.
*/
const request = (options, request_body, response_encoding, response_parser) => {
return new Promise((resolve, reject) => {
const req = https.request(options, res => {
res.setEncoding(response_encoding);
// pick data
let buf = '';
res.on('data', chunk => buf += chunk);
// all data came
res.on('end', () => {
const coords = response_parser(buf);
if (coords !== null) {
return resolve(coords);
} else {
return reject(new Error(E_NOTFOUND));
}
});
});
req.on('socket', socket => {
socket.setTimeout(CONNECTION_TIMEOUT, () => req.abort());
});
req.on('error', err => reject(new Error(E_REQERROR)));
if (options.method === 'POST' && request_body !== null && request_body !== '') {
req.write(request_body);
}
req.end();
});
};
module.exports = {
/**
* Get geographical coordinates from Google GLM MMAP (unofficial API).
* Taken from https://github.com/kolonist/bscoords and modified to parse range.
*
* @param {Number} mcc Mobile Country Code
* @param {Number} mnc Mobile Network Code
* @param {Number} lac Location area code
* @param {Number} cid Cell Identity
* @return {Promise} Object containing lat, lon and range. If cell can not be resolved null.
*/
glm: function (mcc, mnc, lac, cid) {
const options = {
hostname: 'www.google.com',
method : 'POST',
path : '/glm/mmap'
};
const request_body = Buffer.from(
'000e00000000000000000000000000001b0000000000000000000000030000' +
('00000000' + Number(cid).toString(16)).substr(-8) +
('00000000' + Number(lac).toString(16)).substr(-8) +
('00000000' + Number(mnc).toString(16)).substr(-8) +
('00000000' + Number(mcc).toString(16)).substr(-8) +
'ffffffff00000000',
'hex'
);
const response_encoding = 'hex';
/**
* Convert 32-bit hex string into signed integer.
* @param {String} hex Hex string like 'fab1c2d3'.
*/
const hex2int = hex => {
let int = parseInt(hex, 16);
// negative number
if ((int & 0x80000000) !== 0) {
int = int - 0x100000000;
}
return int;
};
const response_parser = buf => {
try {
if (buf.length < 38) {
return null;
}
const coords = {
lat: hex2int(buf.slice(14, 22)) / 1000000,
lon: hex2int(buf.slice(22, 30)) / 1000000,
range: hex2int(buf.slice(30, 38))
};
if (coords.lat === 0 && coords.lon === 0) {
return null;
}
return coords;
} catch(err) {
return null;
}
};
return request(options, request_body, response_encoding, response_parser);
},
/**
* Get geographical coordinates from OpenCellId.
* Taken from https://github.com/kolonist/bscoords and modified to parse range and code.
*
* @param {Number} mcc Mobile Country Code
* @param {Number} mnc Mobile Network Code
* @param {Number} lac Location area code
* @param {Number} cid Cell Identity
* @param {String} key OpenCellId API key
* @return {Promise} Object containing lat, lon, range and status code. If there is a severe error null.
*/
oci: function (mcc, mnc, lac, cid, key) {
const options = {
hostname: 'opencellid.org',
method : 'GET',
path : `/cell/get?key=${key}&mnc=${mnc}&mcc=${mcc}&lac=${lac}&cellid=${cid}`
};
const request_body = null;
const response_encoding = 'utf8';
const response_parser = buf => {
try {
if (RE_OPENCELLID_QUOTA.test(buf) || RE_UNWIREDLABS_QUOTA.test(buf)) {
const coords = {
lat: 0,
lon: 0,
range: 0,
statusCode: 429
};
return coords;
} else if (RE_OPENCELLID_ERROR.test(buf)) {
const coords = {
lat: 0,
lon: 0,
range: 0,
statusCode: 404
};
return coords;
} else {
const coords = {
lat: Number(RE_FETCH_OPENCELLID_LAT.exec(buf)[1]),
lon: Number(RE_FETCH_OPENCELLID_LON.exec(buf)[1]),
range: Number(RE_FETCH_OPENCELLID_RANGE.exec(buf)[1]),
statusCode: 200
};
return coords;
}
} catch(err) {
return null;
}
};
return request(options, request_body, response_encoding, response_parser);
}
};