From 972fdeaa740ca6deab175937a6654549d3859ba5 Mon Sep 17 00:00:00 2001 From: Kelley Robinson <3673341+robinske@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:01:37 -0400 Subject: [PATCH 1/4] Migrate to V2, improve testing --- lookup/assets/index.html | 204 ++++++++++++++++++++++++++---------- lookup/assets/styles.css | 4 +- lookup/functions/lookup.js | 55 ++++++---- lookup/tests/lookup.test.js | 144 +++++++++++++++++++------ 4 files changed, 293 insertions(+), 114 deletions(-) diff --git a/lookup/assets/index.html b/lookup/assets/index.html index 3471bfad..ee7cbd59 100644 --- a/lookup/assets/index.html +++ b/lookup/assets/index.html @@ -70,15 +70,6 @@
-

- -
-

Welcome!

-

Your live application with Twilio is ready to use!

-
-

Phone number lookup

This example shows how to process and parse international phone @@ -86,56 +77,153 @@

Phone number lookup

intl-tel-input - library and detect invalid phone numbers, carrier information, and - caller name with the + library and detect invalid phone numbers, line type, carrier + information, caller name, and more with the Twilio Lookup API.

-

- New data like enhanced Line Type Intelligence and SIM Swap detection - available in V2. - Learn more. -

+
-

Enter your phone number:

+

Enter your phone number

-

Request type:

+

+ Select your data packages + [learn more] +

- -
+ +
- + Line Type Intelligence
-
+
- -
+
+
+
+ +
+
+
+ +
+
+
+ +
+

+ * Additional carrier approval required prior to use. + Submit this form to request access. +

+

+ The + Identity Match + package is also available but requires additional details in the + request. + Learn more in this blog post. +

- - +
@@ -146,58 +234,60 @@

Phone number lookup

diff --git a/lookup/assets/styles.css b/lookup/assets/styles.css index 2bfc4a60..1085d322 100644 --- a/lookup/assets/styles.css +++ b/lookup/assets/styles.css @@ -9,13 +9,13 @@ pre { border-radius: 4px; } -.alert-info { +.info { border-color: #bce8f1; color: #31708f; background-color: #d9edf7; } -.alert-error { +.error { color: #a94442; background-color: #f2dede; border-color: #ebccd1; diff --git a/lookup/functions/lookup.js b/lookup/functions/lookup.js index 3a6e2f2b..0edb9851 100644 --- a/lookup/functions/lookup.js +++ b/lookup/functions/lookup.js @@ -1,46 +1,59 @@ +// pulls nested error codes from response data +function getErrorLinks(data) { + const hasErrorCode = (value) => + value !== null && + typeof value !== 'undefined' && + value.hasOwnProperty('error_code') && + value.error_code !== null; + + const errors = Object.entries(data) + .filter(([_, v]) => hasErrorCode(v)) + .map(([k, v]) => [k, v.error_code]); + + return errors.map(([k, errorCode]) => { + return `${k} error: ${errorCode}`; + }); +} + +exports.getErrorLinks = getErrorLinks; + /** * Lookup - validate a phone number * * This function will tell you whether or not a phone number is valid using Twilio's Lookup API + * It will also provide additional information about the phone number depending on which data packages are requested * * Parameters: * "phone" - string - phone number in E.164 format (https://www.twilio.com/docs/glossary/what-e164) + * "fields" - array - optional - list of fields to return from the Lookup API */ - -// eslint-disable-next-line consistent-return exports.handler = async function (context, event, callback) { const response = new Twilio.Response(); response.appendHeader('Content-Type', 'application/json'); - /* - * uncomment to support CORS - * response.appendHeader('Access-Control-Allow-Origin', '*'); - * response.appendHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); - * response.appendHeader('Access-Control-Allow-Headers', 'Content-Type'); - */ - try { - if (event.phone === '' || typeof event.phone === 'undefined') { + const client = context.getTwilioClient(); + + const { phone, fields } = event; + if (phone === '' || typeof phone === 'undefined') { throw new Error('Missing parameter; please provide a phone number.'); } - const types = typeof event.types === 'object' ? event.types : [event.types]; - const client = context.getTwilioClient(); - - const resp = await client.lookups + const lookups = await client.lookups.v2 .phoneNumbers(event.phone) - .fetch({ type: types }); + .fetch({ fields: fields.join(',') }); - if (types.includes('lti')) { - const { lineTypeIntelligence } = await client.lookups.v2 - .phoneNumbers(event.phone) - .fetch({ fields: 'line_type_intelligence' }); + if (!lookups.valid) { + throw new Error(`Invalid phone number: ${lookups.validationErrors}.`); + } - resp.lineTypeIntelligence = lineTypeIntelligence; + const errors = getErrorLinks(lookups); + if (errors.length > 0) { + throw new Error(`${errors.join('
')}`); } response.setStatusCode(200); - response.setBody(resp); + response.setBody(lookups); return callback(null, response); } catch (error) { console.error(error.message); diff --git a/lookup/tests/lookup.test.js b/lookup/tests/lookup.test.js index 3da22417..1b2e2f3a 100644 --- a/lookup/tests/lookup.test.js +++ b/lookup/tests/lookup.test.js @@ -1,13 +1,19 @@ +/* eslint-disable camelcase */ const lookupFunction = require('../functions/lookup').handler; +const { getErrorLinks } = require('../functions/lookup'); const helpers = require('../../test/test-helper'); const mockFetch = { - fetch: jest.fn(() => Promise.resolve({ sid: 'sid' })), + fetch: jest.fn(() => + Promise.resolve({ + valid: true, + sid: 'sid', + }) + ), }; const mockClient = { lookups: { - phoneNumbers: jest.fn(() => mockFetch), v2: { phoneNumbers: jest.fn(() => mockFetch), }, @@ -18,7 +24,7 @@ const testContext = { getTwilioClient: () => mockClient, }; -describe('international-telephone-input/lookup', () => { +describe('lookup', () => { beforeAll(() => { helpers.setup({}); }); @@ -26,10 +32,12 @@ describe('international-telephone-input/lookup', () => { helpers.teardown(); }); - test('returns an error response when required parameter is missing', (done) => { + test('returns an error response when required phone number parameter is missing', (done) => { const callback = (_err, result) => { - expect(result).toBeDefined(); expect(result._statusCode).toEqual(400); + expect(result._body).toEqual({ + error: 'Missing parameter; please provide a phone number.', + }); done(); }; const event = {}; @@ -38,8 +46,10 @@ describe('international-telephone-input/lookup', () => { test('returns an error response when required parameter is empty', (done) => { const callback = (_err, result) => { - expect(result).toBeDefined(); expect(result._statusCode).toEqual(400); + expect(result._body).toEqual({ + error: 'Missing parameter; please provide a phone number.', + }); done(); }; const event = { @@ -48,57 +58,123 @@ describe('international-telephone-input/lookup', () => { lookupFunction(testContext, event, callback); }); - test('formats types into a list if only given one', (done) => { + test('throws an error with an invalid phone number', (done) => { const event = { - phone: '+17341234567', - types: 'carrier', + phone: '+12345', + fields: ['validation'], + }; + + const mockInvalidFetch = { + fetch: jest.fn(() => + Promise.resolve({ + valid: false, + validationErrors: 'TOO_SHORT', + sid: 'sid', + }) + ), + }; + + const mockInvalidPhoneClient = { + lookups: { + v2: { + phoneNumbers: jest.fn(() => mockInvalidFetch), + }, + }, }; - const expectedParams = { - type: ['carrier'], + + const testInvalidPhoneContext = { + getTwilioClient: () => mockInvalidPhoneClient, }; + const callback = (_err, result) => { - expect(result).toBeDefined(); - expect(result._statusCode).toEqual(200); - expect(mockFetch.fetch).toHaveBeenCalledWith(expectedParams); + expect(result._statusCode).toEqual(400); + expect(result._body).toEqual({ + error: 'Invalid phone number: TOO_SHORT.', + }); done(); }; - lookupFunction(testContext, event, callback); + lookupFunction(testInvalidPhoneContext, event, callback); + }); + + test('transforms data into error links', (done) => { + const data = { + simSwap: { + error_code: 60008, + }, + callForwarding: { + call_forwarding_enabled: null, + error_code: 60607, + }, + }; + + const expected = [ + 'simSwap error: 60008', + 'callForwarding error: 60607', + ]; + + const result = getErrorLinks(data); + expect(result).toEqual(expected); + done(); }); - test('uses v2 api if lti is provided as type', (done) => { + test('throws an error when there are nested errors in the data', (done) => { const event = { phone: '+17341234567', - types: 'lti', + fields: ['validation'], + }; + + const mockErrorsFetch = { + fetch: jest.fn(() => + Promise.resolve({ + valid: true, + simSwap: { + error_code: 60008, + }, + callForwarding: { + call_forwarding_enabled: null, + error_code: 60607, + }, + }) + ), + }; + + const mockErrorsClient = { + lookups: { + v2: { + phoneNumbers: jest.fn(() => mockErrorsFetch), + }, + }, }; - const expectedParams = { - type: ['lti'], + + const testErrorsContext = { + getTwilioClient: () => mockErrorsClient, }; + const callback = (_err, result) => { - expect(result).toBeDefined(); - expect(result._statusCode).toEqual(200); - expect('lineTypeIntelligence' in result._body).toEqual(true); - expect(mockFetch.fetch).toHaveBeenCalledWith(expectedParams); - expect(mockClient.lookups.v2.phoneNumbers).toHaveBeenCalledWith( - event.phone - ); + expect(result._statusCode).toEqual(400); + expect(result._body).toEqual({ + error: + 'simSwap error: 60008
callForwarding error: 60607', + }); done(); }; - lookupFunction(testContext, event, callback); + lookupFunction(testErrorsContext, event, callback); }); test('returns success with valid request', (done) => { const event = { phone: '+17341234567', - types: ['formatting', 'carrier'], - }; - const expectedParams = { - type: ['formatting', 'carrier'], + fields: ['validation', 'line_type_intelligence'], }; + const callback = (_err, result) => { - expect(result).toBeDefined(); expect(result._statusCode).toEqual(200); - expect(mockFetch.fetch).toHaveBeenCalledWith(expectedParams); - expect(mockClient.lookups.phoneNumbers).toHaveBeenCalledWith(event.phone); + expect(mockFetch.fetch).toHaveBeenCalledWith({ + fields: 'validation,line_type_intelligence', + }); + expect(mockClient.lookups.v2.phoneNumbers).toHaveBeenCalledWith( + event.phone + ); done(); }; lookupFunction(testContext, event, callback); From 6202cec19661ac744658fa65d8b215433b867847 Mon Sep 17 00:00:00 2001 From: Kelley Robinson <3673341+robinske@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:28:29 -0400 Subject: [PATCH 2/4] Add changelog entry --- lookup/assets/index.html | 59 +++++++++++++++++++++++++++++----------- lookup/changelog.md | 4 +++ lookup/package.json | 4 +-- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/lookup/assets/index.html b/lookup/assets/index.html index ee7cbd59..b085e79b 100644 --- a/lookup/assets/index.html +++ b/lookup/assets/index.html @@ -145,6 +145,20 @@

Phone number lookup

>
+
+ +
+
Phone number lookup >Submit this form to request access.

-

- The - Identity Match - package is also available but requires additional details in the - request. - Learn more in this blog post. -

- +

Additional Lookup Data Packages

+

+ 👀 + Identity Match + package requires additional details in the request. + Learn more in this blog post. +

+

+ ✏️ + Identity Pre-fill + combines Verify and Lookup for a seamless onboarding experience. + Learn more in this Code Exchange project. +

diff --git a/lookup/changelog.md b/lookup/changelog.md index 4c24d30a..2e4080b7 100644 --- a/lookup/changelog.md +++ b/lookup/changelog.md @@ -1,5 +1,9 @@ # Changelog +## [1.1.0] +### Changed +- Moved all requests to Lookup V2 + ## [1.0.1] ### Added - Adds option for V2 Line Type Intelligence data package diff --git a/lookup/package.json b/lookup/package.json index 25969df0..908af057 100644 --- a/lookup/package.json +++ b/lookup/package.json @@ -1,7 +1,7 @@ { - "version": "1.0.0", + "version": "1.0.1", "private": true, "dependencies": { - "twilio": "^3.61.0" + "twilio": "^5.2.2" } } From aa0296341cbb33fd64ccd2d15d54b15e9f60885d Mon Sep 17 00:00:00 2001 From: Kelley Robinson <3673341+robinske@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:45:56 -0400 Subject: [PATCH 3/4] Remove reassigned number as checkbox option --- lookup/assets/index.html | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/lookup/assets/index.html b/lookup/assets/index.html index b085e79b..ff334554 100644 --- a/lookup/assets/index.html +++ b/lookup/assets/index.html @@ -199,20 +199,6 @@

Phone number lookup

>*
-
- -
-

* Additional carrier approval required prior to use. @@ -232,10 +218,17 @@

Additional Lookup Data Packages

>Identity Match - package requires additional details in the request. + and + Reassigned Number + packages require additional details in the request. Learn more in this blog post.Learn more about Identity Match in this blog post.

From a27cc2cb04ce6e2b7336a1e20013a46b22d2ff59 Mon Sep 17 00:00:00 2001 From: Kelley Robinson <3673341+robinske@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:14:21 -0400 Subject: [PATCH 4/4] Adds more explicit details to error code function --- lookup/functions/lookup.js | 47 +++++++++++++++++++++++++++++++++---- lookup/tests/lookup.test.js | 6 ++--- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/lookup/functions/lookup.js b/lookup/functions/lookup.js index 0edb9851..b17817f0 100644 --- a/lookup/functions/lookup.js +++ b/lookup/functions/lookup.js @@ -1,17 +1,54 @@ -// pulls nested error codes from response data +/** + * Helper function to pull nested error codes from response data + * + * @param {object} data - response data from Twilio Lookup API + * @returns {array} - array of error messages with links to Twilio's error code documentation + * @example + * const data = { + * identityMatch: null, + * callerName: { + * caller_type: "BUSINESS", + * error_code: null, + * }, + * simSwap: { + * error_code: 60008, + * }, + * callForwarding: { + * error_code: 60607, + * } + * }; + * + * getErrorLinks(data); + * // returns ['simSwap error: 60008', 'callForwarding error: 60607'] + */ function getErrorLinks(data) { + /* + * Find a nested error code + * In the example above, this identifies error codes + * in simSwap and callForwarding + */ const hasErrorCode = (value) => value !== null && typeof value !== 'undefined' && value.hasOwnProperty('error_code') && value.error_code !== null; + /* + * Filter out fields that do not have an error code + * and transform the remaining fields to an array. + * In the example above this would return: + * [['simSwap', 60008], ['callForwarding', 60607]] + */ const errors = Object.entries(data) - .filter(([_, v]) => hasErrorCode(v)) - .map(([k, v]) => [k, v.error_code]); + .filter(([_, fieldDetails]) => hasErrorCode(fieldDetails)) + .map(([fieldName, fieldDetails]) => [fieldName, fieldDetails.error_code]); - return errors.map(([k, errorCode]) => { - return `${k} error: ${errorCode}`; + /* + * Format the error codes in a human-readable way + * with links to Twilio's error code documentation + */ + return errors.map(([fieldName, errorCode]) => { + return `${fieldName} error: ${errorCode}`; }); } diff --git a/lookup/tests/lookup.test.js b/lookup/tests/lookup.test.js index 1b2e2f3a..ba80ff2e 100644 --- a/lookup/tests/lookup.test.js +++ b/lookup/tests/lookup.test.js @@ -108,8 +108,8 @@ describe('lookup', () => { }; const expected = [ - 'simSwap error: 60008', - 'callForwarding error: 60607', + 'simSwap error: 60008', + 'callForwarding error: 60607', ]; const result = getErrorLinks(data); @@ -154,7 +154,7 @@ describe('lookup', () => { expect(result._statusCode).toEqual(400); expect(result._body).toEqual({ error: - 'simSwap error: 60008
callForwarding error: 60607', + 'simSwap error: 60008
callForwarding error: 60607', }); done(); };