-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
425 lines (405 loc) · 20.1 KB
/
index.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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
/**
* A node phone number utilities module!
* @module node-phone-utils
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
*/
require('dotenv').config({ silent: true });
var _ = require('lodash');
var PNValidator = require('./validators/phone-number-validator');
var InputValidator = require('./validators/input-validator');
var PNExtractor = require('./extractors/phone-number-extractor');
var PNFormatter = require('./formatters/phone-number-formatter');
//hlr lookup providers
var SmsApiProvider = require('./providers/sms-api-provider');
var HlrLookupsProvider = require('./providers/hlr-lookups-provider');
var providers;
/**
* The main phone-number-utils class for manipulating phone numbers
* @param [options] optional options object. The options.logger can contain a winston (or other) logger and is used for logging. Options can also contain a hlr provider to be used with hlrLookup function. Just set it as options.provider.
* @returns {{isValid: isValid, isMobile: isMobile, toE164: toE164, toNationalNumber: toNationalNumber, PhoneNumberType: *, getType: getType, getCountryCode: getCountryCode, getProviders: getProviders, hlrLookup: hlrLookup, getVersion: getVersion}}
* @constructor
* @throws Error if options were supplied as an invalid or non object.
* @example
*
* var options = {
* logger: winston // console, bunyan , anything that has in info, log, error, warn functions
* provider: hlrLookups provider // any built in or custom provider
* };
* var phoneNumbersUtils = require('node-phone-utils').createInstance(options);
*/
var PhoneNumberUtils = function PhoneNumberUtils(options) {
//phone number manipulators
var pUtilsOptions = options || {};
if (!_.isObject(pUtilsOptions) || _.isArray(pUtilsOptions)) {
throw new Error('Supplied options object must be an object');
}
var pnValidator = PNValidator.createInstance(pUtilsOptions);
var pnExtractor = PNExtractor.createInstance(pUtilsOptions);
var pnFormatter = PNFormatter.createInstance(pUtilsOptions);
return {
/**
*This function checks if an input phone numbers are valid or not.
* @function
* @public
* @param numbers {!string|string[]} string or array of strings indicating E164 formatted phone number(s) to be validated.
* @param [regionCode] {string} optional 2 letter region code string like 'FR' or 'UK'
* @returns {boolean|Object[]} In case numbers is a string it returns a boolean indicating if the number is valid or not. Throws error in case of error.
* In case numbers is an array the return value is an array (order not guaranteed) with objects {isValid: true|false, number: inputNumber}.
* In case of error error is not thrown but another object is put in the array like {isError : true, err: [error Object]}.
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
*
* phoneNumbersUtils.isValid('+3312344321') //=> true
*
* phoneNumbersUtils.isValid(['+3312344321', '+AB123456', '1ABC,!']) //=>
*
* [
* {
* isValid: true,
* number: "+3312344321"
* },
* {
* isValid: false,
* number: "+AB123456"
* },
* {
* isError: true,
* error: [error Object]
* },
* ]
* @throws {Error|TypeError} In case numbers is a string it throws errors that occurred in validation of input as well as in process of checking. In case numbers is a string array, error is thrown if numbers is empty array, otherwise an object {isError: true, error {Error}} is put into the returned array.
*/
isValid: function isValid(numbers, regionCode) {
return getProcessedResult(numbers, regionCode, 'isValid', pnValidator.isValid, pnValidator);
},
/**
* This function checks if the phone number(s) are mobile or not.
* @public
* @function
* @param numbers {!string|string[]} string or array of strings indicating E164 formatted phone number(s) to be checked.
* @param [regionCode] {string} optional 2 letter region code string like 'FR' or 'UK'
* @returns {boolean|Object[]} In case numbers is a string it returns a boolean indicating if the number is valid or not. Throws error in case of error.
* In case numbers is an array the return value is an array (order not guaranteed) with objects {isValid: true|false, number: inputNumber}.
* In case of error error is not thrown but another object is put in the array like {isError : true, err: [error Object]}.
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
*
* phoneNumbersUtils.isMobile('+3312344321') //=> true
*
* phoneNumbersUtils.isMobile(['+3312344321', '+AB123456', '1ABC,!']) // =>
*
* [
* {
* isMobile: true,
* number: "+3312344321"
* },
* {
* isMobile: false,
* number: "+AB123456"
* },
* {
* isError: true,
* error: [error Object]
* },
* ]
* @throws {Error|TypeError} In case numbers is a string it throws errors that occurred in validation of input as well as in the process of checking. In case numbers is a string array, error is thrown if numbers is empty array otherwise an object {isError: true, error {Error}} is put into the returned array.
*/
isMobile: function isMobile(numbers, regionCode) {
return getProcessedResult(numbers, regionCode, 'isMobile', pnValidator.isMobile, pnValidator);
},
/**
* This function converts phone number(s) to E164 format
* @function
* @public
* @param numbers {!string|string[]} string or array of strings indicating phone number(s) to be formatted to e164 form.
* @param [regionCode] {string} optional 2 letter region code string like 'FR' or 'UK'
* @returns {string|Object[]} In case numbers is a string it returns a string representing a phone number in E164 format. Throws error otherwise.
* In case numbers is an array the return value is an array (order not guaranteed) with objects {e164: "number", number: inputNumber}.
* In case of error, error is not thrown but another object is put in the array like {isError : true, err: [error Object]}.
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
*
* phoneNumbersUtils.toE164('33 123 44 321') //=> '+3312344321'
*
* phoneNumbersUtils.toE164(['33 123 44 321', '33 123 44 322', '1ABC,!']) //=>
*
* [
* {
* e164: "+3312344321"
* number: "33 123 44 321"
* },
* {
* e164: "+3312344322"
* number: "33 123 44 322"
* },
* {
* isError: true,
* error: [error Object]
* },
* ]
* @throws {Error|TypeError} In case numbers is a string it throws errors that occurred in processing of input. In case numbers is a string array, error is thrown if numbers is empty array, otherwise an object {isError: true, error {Error}} is put into the returned array.
*/
toE164: function toE164(numbers, regionCode) {
return getProcessedResult(numbers, regionCode, 'e164', pnFormatter.toE164, pnFormatter);
},
/**
* This function converts phone number(s) to national numbers format
* @public
* @function
* @param numbers {!string|string[]} string or array of strings indicating E164 formatted phone number(s) to be checked.
* @param [regionCode] {string} optional 2 letter region code string like 'FR' or 'UK'
* @returns {string|Object[]} In case numbers is a string it returns a string representing a phone number in national format. Throws error otherwise.
* In case numbers is an array the return value is an array (order not guaranteed) with objects {nationalNumber: "number", number: inputNumber}.
* In case of error, error is not thrown but another object is put in the array like {isError : true, err: [error Object]}.
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
*
* phoneNumbersUtils.toNationalNumber('+33892696992') //=> '0 892 69 69 92'
*
* phoneNumbersUtils.toNationalNumber(['+33892696992', '+33969396949', '1ABC,!']) //=>
* [
* {
* nationalNumber: "0 892 69 69 92"
* number: "+33892696992"
* },
* {
* nationalNumber: "09 69 39 69 49"
* number: "+33969396949"
* },
* {
* isError: true,
* error: [error Object]
* },
* ]
* @throws {Error|TypeError} In case numbers is a string it throws errors that occurred in processing of input. In case numbers is a string array, error is thrown if numbers is empty array, otherwise an object {isError: true, error {Error}} is put into the returned array.
*/
toNationalNumber: function toNationalNumber(numbers, regionCode) {
return getProcessedResult(numbers, regionCode, 'nationalNumber', pnFormatter.toNationalNumber, pnFormatter);
},
/**
* Phone Number Type map useful for extracting human readable results from getType.
* This is basically a passthrough of i18n.phonenumbers.PhoneNumberType
* @typeDef {Object.<string, number>} PhoneNumberType
* @public
* @example
* i18n.phonenumbers.PhoneNumberType = {
* FIXED_LINE: 0,
* MOBILE: 1,
* // In some regions (e.g. the USA), it is impossible to distinguish between
* // fixed-line and mobile numbers by looking at the phone number itself.
* FIXED_LINE_OR_MOBILE: 2,
* // Freephone lines
* TOLL_FREE: 3,
* PREMIUM_RATE: 4,
* // The cost of this call is shared between the caller and the recipient, and
* // is hence typically less than PREMIUM_RATE calls. See
* // http://en.wikipedia.org/wiki/Shared_Cost_Service for more information.
* SHARED_COST: 5,
* // Voice over IP numbers. This includes TSoIP (Telephony Service over IP).
* VOIP: 6,
* // A personal number is associated with a particular person, and may be routed
* // to either a MOBILE or FIXED_LINE number. Some more information can be found
* // here: http://en.wikipedia.org/wiki/Personal_Numbers
* PERSONAL_NUMBER: 7,
* PAGER: 8,
* // Used for 'Universal Access Numbers' or 'Company Numbers'. They may be
* // further routed to specific offices, but allow one number to be used for a
* // company.
* UAN: 9,
* // Used for 'Voice Mail Access Numbers'.
* VOICEMAIL: 10,
* // A phone number is of type UNKNOWN when it does not fit any of the known
* // patterns for a specific region.
* UNKNOWN: -1
* };
*/
PhoneNumberType: pnExtractor.PhoneNumberType,
/**
* This function gets the type of the phone number
* @function
* @public
* @param numbers {!string|string[]} string or array of strings indicating E164 formatted phone numbers to be checked.
* @param [regionCode] {string} optional 2 letter region code string like 'FR' or 'UK'
* @returns {PhoneNumberType|Object[]} In case of single phone number (string) it phone number type value (one of {@link PhoneNumberType} values), error thrown otherwise. In case numbers is array then array of objects like {type: {number}, number:inputNumber} is returned. If one of entries in array caused an error, then and error object is put in that place of the returned array like {isError: true; error: [error Object]}.
* @throws {Error|TypeError} In case numbers is a string it throws errors that occurred in processing of input and in case numbers is a string array, then error is thrown if numbers is empty array otherwise an object {isError: true, error {Error}} is put into the returned array.
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
*
* phoneNumbersUtils.getType('+3312344321') // => 33
*
* phoneNumbersUtils.getType(['+3312344321', '+3312344322', '1ABC,!']) //=>
*
* [
* {
* type: 4,
* number: "+3312344321"
* },
* {
* type: 1,
* number: "+3312344322"
* },
* {
* isError: true,
* error: [error Object]
* },
* ]
*/
getType: function getType(numbers, regionCode) {
return getProcessedResult(numbers, regionCode, 'type', pnExtractor.getType, pnExtractor);
},
/**
* This function gets country codes from phone numbers
* @function
* @public
* @param numbers {!string|string[]} string or array of strings indicating E164 formatted phone number(s) to be checked.
* @param [regionCode] {string} optional 2 letter region code string like 'FR' or 'UK'
* @returns {number|Object[]} In case of single phone number (string) returns a single country code or throws error otherwise. In case numbers is array then array of objects like {countryCode: {number}, number:inputNumber} is returned. If one of the entries in array caused an error, then and error object is put in that place of the returned array in the form od {isError: true; error: [error Object]}.
* @throws {Error|TypeError} In case numbers is a string it throws errors that occurred in processing of input and in case numbers is a string array, then error is thrown if numbers is empty array otherwise an object {isError: true, error {Error}} is put into the returned array.
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
*
* phoneNumbersUtils.getCountryCode('+3312344321') //=> 33
*
* phoneNumbersUtils.getCountryCode(['+3312344321', '+3312344322', '1ABC,!']) //=>
*
* [
* {
* countryCode: 33,
* number: "+3312344321"
* },
* {
* countryCode: 33,
* number: "+3312344322"
* },
* {
* isError: true,
* error: [error Object]
* },
* ]
*/
getCountryCode: function getCountryCode(numbers, regionCode) {
return getProcessedResult(numbers, regionCode, 'countryCode', pnExtractor.getCountryCode, pnExtractor);
},
/**
* Get built in HLR Lookups providers
* @returns {{smsApi: (SMSAPILookupsProvider), hlrLookups: (HLRLookupsProvider)}}
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
* var smsApiProvider = phoneNumbersUtils.getProviders().smsApi;
* var hlrLookupsProvider = phoneNumbersUtils.getProviders().hlrLookups;
* // Do something with the providers.
*/
getProviders: function getProviders() {
if (!providers) {
providers = {
smsApi: new SmsApiProvider('smsapi.com', process.env.SMSAPI_USERNAME, process.env.SMSAPI_HASHED_PASSWORD, pUtilsOptions.logger),
hlrLookups: new HlrLookupsProvider('hlr-lookups.com', process.env.HLR_LOOKUPS_USERNAME, process.env.HLR_LOOKUPS_PASSWORD, pUtilsOptions.logger)
};
}
return providers;
},
/**
* This function does an HLR lookup of an E164 phone number and returns the results
* @function
* @public
* @param e164Number string or array of string indicating E164 phone numbers to be checked.
* @param [provider] {Object}
* @returns {Promise} HLR info if resolved and error if rejected.
*/
hlrLookup: function hlrLookup(e164Number, provider) {
var _provider = provider || pUtilsOptions.provider;
if (!InputValidator.isValidHLRLookupProvider(_provider)) {
return Promise.reject(new Error('Invalid HLR lookup provider supplied. Make sure it is an object with name property and a hlrLookup function.'));
} else {
return Promise.resolve(_provider.hlrLookup(e164Number));
}
},
/**
* Provides a client and middleware for doing async bulk number hlr lookups.
* @function
* @public
* @param {object} options configuration options for returned lookupClient and lookupMiddleware
* @returns {{lookupClient: ({hlrLookup}|{hlrLookup: hlrLookup}), lookupMiddleware: Function}} Results of lookups are first
* gathered from cache and for those phone numbers in cache hlrLookup via provider is done. If all numbers are found in cache then
* no request to hlrLookup is made and middleware will not be triggered. Results are reported in options.doneCallback.
* @throws {Error}
*
* @example
* var phoneNumbersUtils = require('node-phone-utils').createInstance();
* var options = {
* provider: phoneUtils.getProviders().hlrLookups, // required. valid provider
* logger: logger, // optional a valid logger
* callbackUrl: 'http: //localhost:3000/hlr/lookups', // required. a valid callbackUrl to which you set lookupMiddleware (POST)
* callbackIdQSParam: 'idx', // required. a query string param that is added to callback url to identify requests
* middlewareSend: true, // optional. override (default true) if you wish to do a res.send in some other middleware
* cache: cacheStore, // required. a cache-manger cache store
* lookupTimeout: 30, //seconds. required. For automatic cleanup and maximum time entire lookup process can take. Should not be more then 10 minutes (depending on max number of numbers being looked up)
* // optional, a function that is called when middleware processes async lookup request and generates result
* resultCallback: function (err, results) {
* // a result was processed and available
* return results;
* },
* // optional, a function that is called when all phone numbers from asyncHlrLookup have been processed and results gathered
* doneCallback: function (err, results) {
* // do what you need when the entire lookup is done.
* return results;
* }
* };
* var asyncLookupTools = phoneNumbersUtils.getAsyncHlrLookup(options);
* // set asyncLookupTools.lookupMiddleware to a route for your callbackUrl
* // initiate the lookup for a big array of numbers
* asyncLookupTools.lookupClient.hlrLookup(['+3312344321', '+3312344322'], callbackUrlOverride)
*/
getAsyncHlrLookup: function getAsyncHlrLookup(options) {
return {
lookupClient: require('./clients')(options),
lookupMiddleware: require('./middleware')(options)
};
},
/**
* The version of this library (sync call)
* @public
* @returns {string} Version of the library.
*/
getVersion: function getVersion() {
return require('../package.json').version;
}
};
};
/**
* Main instantiation function that creates another PhoneNumberUtils object
* @param [options] Optional options object where you can supply logger, provider options
* @returns {PhoneNumberUtils} {@type PhoneNumberUtils}
* @throws {Error} if something went wrong.
*/
module.exports.createInstance = function createInstance(options) {
return new PhoneNumberUtils(options);
};
//helper functions
/**
* @private
* @param numbers {string|string[]} string or array of strings indicating E164 formatted phone number(s) to be validated.
* @param regionCode {string} 2 letter region code string like 'FR' or 'UK'. Can be null or falsy.
* @param arrayCaseObjPropName {string} name of the field that will be added to object in case numbers is array. Usually the result of type of the check e.g. isMobile, isValid, type...)
* @param processingFunc function to process each number.
* @param thisRef reference to this for processingFunc
* @returns {*} the result of processingFunc if numbers is not array. if numbers is array then array of objects that contain the result of processingFunc in a arrayCaseObjPropName property and a field named number that is a passed in number or an error object in case of error {isError: true , error :[errorObject]}
*/
function getProcessedResult(numbers, regionCode, arrayCaseObjPropName, processingFunc, thisRef) {
if (_.isArray(numbers) && !_.isEmpty(numbers)) {
return _.map(numbers, function (number) {
var result = {};
try {
result[arrayCaseObjPropName] = processingFunc.call(thisRef, number, regionCode);
result.number = number;
} catch (e) {
result.isError = true;
result.error = e;
}
return result;
});
} else {
return processingFunc.call(thisRef, numbers, regionCode);
}
}