├── .eslintrc ├── .eslintrc.json ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── LICENSE ├── README.md ├── examples ├── account-info.ts ├── error-handling.ts ├── jobs-create.ts ├── jobs-delete.ts ├── jobs-download.ts ├── jobs-parse.ts ├── jobs-results.ts ├── jobs-search.ts ├── jobs-start.ts ├── jobs-status.ts ├── poe-confirm.ts └── single-check.ts ├── jest.config.cjs ├── package-lock.json ├── package.json ├── src ├── Account.ts ├── Errors.ts ├── HttpsClient.ts ├── Jobs.ts ├── NeverBounce.ts ├── POE.ts ├── Single.ts ├── VerificationObject.ts ├── index.ts └── types.ts ├── test-cjs.cjs ├── test-esm.js ├── tests ├── Account.test.ts ├── Errors.test.ts ├── Jobs.test.ts ├── NeverBounce.test.ts ├── POE.test.ts ├── Single.test.ts ├── VerificationObject.test.ts ├── setup.ts └── tsconfig.json ├── tsconfig.cjs.json └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "rules": { 7 | "quotes": [2, "single"], 8 | "comma-dangle": 0, 9 | "indent": [2, 4, {"SwitchCase": 1}], 10 | "no-console": 0, 11 | "no-alert": 0 12 | } 13 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/recommended" 6 | ], 7 | "plugins": ["@typescript-eslint"], 8 | "env": { 9 | "node": true, 10 | "es2020": true, 11 | "mocha": true 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2020, 15 | "sourceType": "module" 16 | }, 17 | "rules": { 18 | "no-console": "warn", 19 | "@typescript-eslint/explicit-module-boundary-types": "warn", 20 | "@typescript-eslint/no-explicit-any": "warn" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [20.x, 18.x] 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'npm' 26 | 27 | - name: Install dependencies 28 | run: npm ci 29 | 30 | - name: Run tests 31 | run: npm test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 30 | node_modules 31 | ### SublimeText template 32 | # cache files for sublime text 33 | *.tmlanguage.cache 34 | *.tmPreferences.cache 35 | *.stTheme.cache 36 | 37 | # workspace files are user-specific 38 | *.sublime-workspace 39 | 40 | # project files should be checked into the repository, unless a significant 41 | # proportion of contributors will probably not be using SublimeText 42 | # *.sublime-project 43 | 44 | # sftp configuration file 45 | sftp-config.json 46 | ### JetBrains template 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 48 | 49 | *.iml 50 | 51 | ## Directory-based project format: 52 | .idea/ 53 | # if you remove the above rule, at least ignore the following: 54 | 55 | # User-specific stuff: 56 | # .idea/workspace.xml 57 | # .idea/tasks.xml 58 | # .idea/dictionaries 59 | 60 | # Sensitive or high-churn files: 61 | # .idea/dataSources.ids 62 | # .idea/dataSources.xml 63 | # .idea/sqlDataSources.xml 64 | # .idea/dynamic.xml 65 | # .idea/uiDesigner.xml 66 | 67 | # Gradle: 68 | # .idea/gradle.xml 69 | # .idea/libraries 70 | 71 | # Mongo Explorer plugin: 72 | # .idea/mongoSettings.xml 73 | 74 | ## File-based project format: 75 | *.ipr 76 | *.iws 77 | 78 | ## Plugin-specific files: 79 | 80 | # IntelliJ 81 | /out/ 82 | 83 | # mpeltonen/sbt-idea plugin 84 | .idea_modules/ 85 | 86 | # JIRA plugin 87 | atlassian-ide-plugin.xml 88 | 89 | # Crashlytics plugin (for Android Studio and IntelliJ) 90 | com_crashlytics_export_strings.xml 91 | crashlytics.properties 92 | crashlytics-build.properties 93 | 94 | # TypeScript build output 95 | dist/ 96 | 97 | # Environment files 98 | .env* 99 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | .idea/ 3 | .env* -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | strict-ssl=false 3 | proxy=null 4 | https-proxy=null 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 NeverBounce 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | Build Status 5 | 6 | 7 | 8 |

9 | 10 | > Looking for the V4 API wrapper with JavaScript? Check out version 4.x.x. 11 | 12 | This is the official NeverBounce API NodeJS wrapper. It provides helpful methods to quickly implement our API in your NodeJS applications. Version 5.0.0 has been completely rewritten in TypeScript and requires Node.js 18 or higher 13 | 14 | **This package is not suitable for use in the browser! Only use it in server side applications!** 15 | 16 | ## Breaking Changes in 5.0.0 17 | 18 | - Requires Node.js 18 or higher 19 | - Converted to TypeScript with full type definitions 20 | - Uses ES Modules instead of CommonJS 21 | - Uses modern fetch API instead of https module 22 | - Improved error handling with proper TypeScript types 23 | 24 | ## Installation 25 | 26 | To install use the following command: 27 | 28 | ```bash 29 | $ npm install neverbounce --save 30 | ``` 31 | 32 | ## Basic Usage 33 | 34 | >**The API username and secret key used to authenticate V3 API requests will not work to authenticate V4 API requests.** If you are attempting to authenticate your request with the 8 character username or 12-16 character secret key the request will return an `auth_failure` error. The API key used for the V4 API will look like the following: `secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`. To create new V4 API credentials please go [here](https://app.neverbounce.com/apps/custom-integration/new). 35 | 36 | ### Example with async/await 37 | 38 | ```ts 39 | import NeverBounce from 'neverbounce'; 40 | 41 | // Initialize NeverBounce client 42 | const client = new NeverBounce({apiKey: 'secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}); 43 | 44 | // Verify an email with async/await 45 | async function verifyEmail() { 46 | try { 47 | // You can request additional information like address_info and credits_info 48 | const result = await client.single.check('support@neverbounce.com', true, true); 49 | 50 | console.log('Result: ' + result.getResult()); // prints: "valid" 51 | console.log('Result (numeric): ' + result.getNumericResult()); 52 | console.log('Is Valid? ' + result.is(NeverBounce.result.valid)); 53 | 54 | // Access the response data with proper typing 55 | const response = result.getResponse(); 56 | 57 | if (response.address_info) { 58 | console.log('Host: ' + response.address_info.host); 59 | } 60 | 61 | if (response.credits_info) { 62 | console.log('Free Credits Used: ' + response.credits_info.free_credits_used); 63 | console.log('Paid Credits Used: ' + response.credits_info.paid_credits_used); 64 | } 65 | } catch (err) { 66 | // Errors are thrown and can be caught to handle specific error types 67 | if (err instanceof Error) { 68 | switch(err.type) { 69 | case NeverBounce.errors.AuthError: 70 | // The API credentials used are bad, have you reset them recently? 71 | break; 72 | case NeverBounce.errors.BadReferrerError: 73 | // The script is being used from an unauthorized source, you may need to 74 | // adjust your app's settings to allow it to be used from here 75 | break; 76 | case NeverBounce.errors.ThrottleError: 77 | // Too many requests in a short amount of time, try again shortly or adjust 78 | // your rate limit settings for this application in the dashboard 79 | break; 80 | case NeverBounce.errors.GeneralError: 81 | // A non recoverable API error occurred check the message for details 82 | break; 83 | default: 84 | // Other non specific errors 85 | console.error('Error:', err.message); 86 | break; 87 | } 88 | } else { 89 | console.error('Unknown error:', err); 90 | } 91 | } 92 | } 93 | 94 | verifyEmail(); 95 | ``` 96 | 97 | ### Example with Promises (then/catch) 98 | 99 | ```ts 100 | import NeverBounce from 'neverbounce'; 101 | 102 | // Initialize NeverBounce client 103 | const client = new NeverBounce({apiKey: 'secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}); 104 | 105 | // Verify an email with Promise syntax 106 | client.single.check('support@neverbounce.com', true, true) 107 | .then(result => { 108 | console.log('Result: ' + result.getResult()); // prints: "valid" 109 | console.log('Result (numeric): ' + result.getNumericResult()); 110 | console.log('Is Valid? ' + result.is(NeverBounce.result.valid)); 111 | 112 | // Access the response data with proper typing 113 | const response = result.getResponse(); 114 | 115 | if (response.address_info) { 116 | console.log('Host: ' + response.address_info.host); 117 | } 118 | }) 119 | .catch(err => { 120 | // Handle errors with type checking 121 | if (err instanceof Error) { 122 | switch(err.type) { 123 | case NeverBounce.errors.AuthError: 124 | console.error('Auth Error:', err.message); 125 | break; 126 | case NeverBounce.errors.BadReferrerError: 127 | console.error('Bad Referrer Error:', err.message); 128 | break; 129 | case NeverBounce.errors.ThrottleError: 130 | console.error('Throttle Error:', err.message); 131 | break; 132 | case NeverBounce.errors.GeneralError: 133 | console.error('General Error:', err.message); 134 | break; 135 | default: 136 | console.error('Error:', err.message); 137 | break; 138 | } 139 | } else { 140 | console.error('Unknown error:', err); 141 | } 142 | }); 143 | ``` 144 | 145 | For more information you can check out the `/examples` directory contained within the repository or visit our official documentation [here](https://developers.neverbounce.com/v4.2/reference). 146 | 147 | Constants 148 | --- 149 | 150 | The library exposes several constants that make working with jobs, verification results and errors easier. They can be accessed from the root `NeverBounce` object via the `result`, `job`, and `errors` properties. 151 | 152 | ## Running Examples 153 | 154 | There are several examples contained within the `/examples` directory included in this repo. The examples are available in both JavaScript and TypeScript. 155 | 156 | ### Setting Up Environment Variables 157 | 158 | The examples use environment variables for configuration. Create a `.env` file in the project root with the following content (substituting in your own API key): 159 | 160 | ``` 161 | NEVERBOUNCE_API_KEY=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 162 | ``` 163 | 164 | ### Building and Running as JavaScript 165 | 166 | Since the library is written in TypeScript, you'll need to compile it first to run the examples as JavaScript: 167 | 168 | ```bash 169 | # Build the project 170 | npm run build 171 | 172 | # Run an example (after compilation) 173 | node dist/examples/account-info.js 174 | ``` 175 | 176 | ### Running TypeScript Examples 177 | 178 | To run the TypeScript examples, use the `example` script with the path to the TypeScript example: 179 | 180 | ```bash 181 | npm run example examples/single-check.ts 182 | ``` 183 | 184 | This will execute the TypeScript file directly using ts-node without requiring a separate compilation step. 185 | -------------------------------------------------------------------------------- /examples/account-info.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import dotenv from 'dotenv'; 3 | import { AccountInfoResponse } from '../src/types.js'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Get account info 14 | client.account.info() 15 | .then((resp: AccountInfoResponse) => console.log('[THEN]', resp)) 16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 17 | 18 | // Alternative using async/await 19 | async function getAccountInfo(): Promise { 20 | try { 21 | const info: AccountInfoResponse = await client.account.info(); 22 | console.log('[ASYNC] Account Info:', info); 23 | } catch (err) { 24 | if (err instanceof Error) { 25 | console.error('[ASYNC] Error:', err.message); 26 | } else { 27 | console.error('[ASYNC] Unknown error occurred'); 28 | } 29 | } 30 | } 31 | 32 | getAccountInfo(); 33 | -------------------------------------------------------------------------------- /examples/error-handling.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import _Error from '../src/Errors.js'; 3 | import VerificationObject from '../src/VerificationObject.js'; 4 | 5 | 6 | // Initialize NeverBounce client 7 | // api key has been intentionally left out to trigger an error 8 | const client = new NeverBounce({}); 9 | 10 | // Verify an email 11 | client.single.check('mike@neverbounce.com', true, true) 12 | .then((result: VerificationObject) => console.log('[THEN]', result)) 13 | .catch((err: any) => { 14 | switch(err.type) { 15 | case NeverBounce.errors.AuthError: 16 | // The API credentials used are bad, have you reset them recently? 17 | console.log('[THEN]', err.message); 18 | break; 19 | case NeverBounce.errors.BadReferrerError: 20 | // The script is being used from an unauthorized source, you may need to 21 | // adjust your app's settings to allow it to be used from here 22 | console.log('[THEN]', err.message); 23 | break; 24 | case NeverBounce.errors.ThrottleError: 25 | // Too many requests in a short amount of time, try again shortly or adjust 26 | // your rate limit settings for this application in the dashboard 27 | console.log('[THEN]', err.message); 28 | break; 29 | case NeverBounce.errors.GeneralError: 30 | // A non recoverable API error occurred check the message for details 31 | console.log('[THEN]', err.message); 32 | break; 33 | default: 34 | // Other non specific errors 35 | console.log('[THEN]', err); 36 | break; 37 | } 38 | }); 39 | 40 | // Alternative using async/await 41 | async function checkEmailWithErrorHandling(): Promise { 42 | try { 43 | // This will fail due to missing API key 44 | const result = await client.single.check('mike@neverbounce.com', true, true); 45 | console.log('[ASYNC]', result); 46 | } catch (err) { 47 | if (err instanceof _Error) { 48 | switch(err.type) { 49 | case NeverBounce.errors.AuthError: 50 | console.log('[ASYNC] Auth Error:', err.message); 51 | break; 52 | case NeverBounce.errors.BadReferrerError: 53 | console.log('[ASYNC] Bad Referrer Error:', err.message); 54 | break; 55 | case NeverBounce.errors.ThrottleError: 56 | console.log('[ASYNC] Throttle Error:', err.message); 57 | break; 58 | case NeverBounce.errors.GeneralError: 59 | console.log('[ASYNC] General Error:', err.message); 60 | break; 61 | default: 62 | console.log('[ASYNC] Unknown Error Type:', err.message); 63 | break; 64 | } 65 | } else if (err instanceof Error) { 66 | console.log('[ASYNC] Standard Error:', err.message); 67 | } else { 68 | console.log('[ASYNC] Unknown Error:', err); 69 | } 70 | } 71 | } 72 | 73 | // Run the async version 74 | checkEmailWithErrorHandling(); 75 | -------------------------------------------------------------------------------- /examples/jobs-create.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { JobCreationResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Verify a list of emails 14 | client.jobs.create( 15 | [ 16 | { 17 | id: '12345', 18 | email: 'support@neverbounce.com', 19 | name: 'Fred McValid' 20 | }, 21 | { 22 | id: '12346', 23 | email: 'invalid@neverbounce.com', 24 | name: 'Bob McInvalid' 25 | } 26 | ], 27 | NeverBounce.job.inputType.supplied, // Either `supplied` or `remote_url` 28 | 'Created from Array.csv', // Friendly name that can be used to identify job 29 | false, 30 | true, 31 | ) 32 | .then((resp: JobCreationResponse) => console.log('[THEN] Job ID: ' + resp.job_id)) 33 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 34 | 35 | // Alternative using async/await 36 | async function createJob(): Promise { 37 | try { 38 | const emails: any[] = [ 39 | { 40 | id: '12345', 41 | email: 'support@neverbounce.com', 42 | name: 'Fred McValid' 43 | }, 44 | { 45 | id: '12346', 46 | email: 'invalid@neverbounce.com', 47 | name: 'Bob McInvalid' 48 | } 49 | ]; 50 | const result = await client.jobs.create( 51 | emails, 52 | NeverBounce.job.inputType.supplied, 53 | 'Created from Array.csv', 54 | false, 55 | true, 56 | ); 57 | 58 | console.log('[ASYNC] Job created successfully!'); 59 | console.log('[ASYNC] Job ID:', result.job_id); 60 | } catch (err) { 61 | if (err instanceof Error) { 62 | console.error('[ASYNC] Error creating job:', err.message); 63 | } else { 64 | console.error('[ASYNC] Unknown error occurred'); 65 | } 66 | } 67 | } 68 | 69 | // Run the async version 70 | createJob(); 71 | -------------------------------------------------------------------------------- /examples/jobs-delete.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { JobDeleteResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Delete a job 14 | client.jobs.delete(25999818) 15 | .then((resp: JobDeleteResponse) => console.log('[THEN]', resp)) 16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 17 | 18 | // Alternative using async/await 19 | async function deleteJob(jobId: number): Promise { 20 | try { 21 | const result = await client.jobs.delete(jobId); 22 | console.log('[ASYNC] Job deleted successfully!'); 23 | console.log('[ASYNC] status:', result.status); 24 | } catch (err) { 25 | if (err instanceof Error) { 26 | console.error('[ASYNC] Error deleting job:', err.message); 27 | } else { 28 | console.error('[ASYNC] Unknown error occurred'); 29 | } 30 | } 31 | } 32 | 33 | // Run the async version with a specific job ID 34 | deleteJob(25999817); 35 | -------------------------------------------------------------------------------- /examples/jobs-download.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { JobDownloadRequest } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Empty query object for download 14 | const query: Partial = {}; 15 | 16 | // Download a job's results 17 | client.jobs.download(25999836, query) 18 | .then((resp: string) => console.log('[THEN]', resp)) 19 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 20 | 21 | // Alternative using async/await 22 | async function downloadJobResults(jobId: number): Promise { 23 | try { 24 | const csvData = await client.jobs.download(jobId); 25 | console.log('[ASYNC] Job results downloaded successfully!'); 26 | console.log('[ASYNC] CSV Data (first 100 chars):', csvData.substring(0, 100) + '...'); 27 | 28 | // In a real application, you might want to save this to a file 29 | // For example: 30 | // import fs from 'fs'; 31 | // fs.writeFileSync(`job-${jobId}-results.csv`, csvData); 32 | } catch (err) { 33 | if (err instanceof Error) { 34 | console.error('[ASYNC] Error downloading job results:', err.message); 35 | } else { 36 | console.error('[ASYNC] Unknown error occurred'); 37 | } 38 | } 39 | } 40 | 41 | // Run the async version with a specific job ID 42 | downloadJobResults(25999835); 43 | -------------------------------------------------------------------------------- /examples/jobs-parse.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { ApiResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Parse a job 14 | client.jobs.parse(25999824) 15 | .then((resp: ApiResponse) => console.log('[THEN]', resp)) 16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 17 | 18 | // Alternative using async/await 19 | async function parseJob(jobId: number): Promise { 20 | try { 21 | const result = await client.jobs.parse(jobId); 22 | console.log('[ASYNC] Job parsing initiated successfully!'); 23 | console.log('[ASYNC] Response:', result); 24 | } catch (err) { 25 | if (err instanceof Error) { 26 | console.error('[ASYNC] Error parsing job:', err.message); 27 | } else { 28 | console.error('[ASYNC] Unknown error occurred'); 29 | } 30 | } 31 | } 32 | 33 | // Run the async version with a specific job ID 34 | parseJob(25999825); 35 | -------------------------------------------------------------------------------- /examples/jobs-results.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { JobResultsRequest, JobResultsResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Query parameters for job results 14 | const query: Partial = { 15 | // page: 1, // Page to start from 16 | // per_page: 10, // Number of items per page 17 | }; 18 | 19 | // Get job results 20 | client.jobs.results(25999836, query) 21 | .then((resp: JobResultsResponse) => { 22 | console.log('[THEN] Results Count: ' + resp.results.length); 23 | console.log('[THEN] Results: ' + resp.results.map(result => 24 | result.data.email + `(${result.verification.result})` 25 | )); 26 | }) 27 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 28 | 29 | // Alternative using async/await 30 | async function getJobResults(jobId: number, options: Partial = {}): Promise { 31 | try { 32 | const response = await client.jobs.results(jobId, options); 33 | 34 | console.log('[ASYNC] Job results retrieved successfully!'); 35 | console.log(`[ASYNC] Total results: ${response.total_results}`); 36 | console.log(`[ASYNC] Total pages: ${response.total_pages}`); 37 | console.log(`[ASYNC] Current page: ${response.query.page}`); 38 | 39 | // Display the first few results 40 | if (response.results.length > 0) { 41 | console.log('\n[ASYNC] Sample results:'); 42 | response.results.slice(0, 5).forEach((result, index) => { 43 | console.log(`[ASYNC] ${index + 1}. ${result.data.email} - Result: ${result.verification.result}`); 44 | }); 45 | } else { 46 | console.log('[ASYNC] No results found.'); 47 | } 48 | } catch (err) { 49 | if (err instanceof Error) { 50 | console.error('[ASYNC] Error retrieving job results:', err.message); 51 | } else { 52 | console.error('[ASYNC] Unknown error occurred'); 53 | } 54 | } 55 | } 56 | 57 | // Run the async version with a specific job ID 58 | getJobResults(25999836, { page: 1, per_page: 10 }); 59 | -------------------------------------------------------------------------------- /examples/jobs-search.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { JobSearchRequest, JobSearchResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Search parameters 14 | const searchParams: JobSearchRequest = { 15 | // job_id: 10000, // Filter jobs based on id 16 | // page: 1, // Page to start from 17 | // per_page: 10, // Number of items per page 18 | }; 19 | 20 | // Search jobs 21 | client.jobs.search(searchParams) 22 | .then((resp: JobSearchResponse) => { 23 | console.log('[THEN] Jobs Returned: ' + resp.results.length); 24 | console.log('[THEN] Job IDs: ' + resp.results.map(job => job.id)); 25 | }) 26 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 27 | 28 | // Alternative using async/await 29 | async function searchJobs(params: JobSearchRequest = {}): Promise { 30 | try { 31 | const response = await client.jobs.search(params); 32 | 33 | console.log('[ASYNC] Jobs search completed successfully!'); 34 | console.log(`[ASYNC] Total jobs found: ${response.total_results}`); 35 | console.log(`[ASYNC] Total pages: ${response.total_pages}`); 36 | console.log(`[ASYNC] Current page: ${response.query.page}`); 37 | 38 | // Display the jobs 39 | if (response.results.length > 0) { 40 | console.log('\n[ASYNC] Jobs:'); 41 | response.results.forEach((job, index) => { 42 | console.log(`[ASYNC] ${index + 1}. ID: ${job.id}, Filename: ${job.filename}, Status: ${job.job_status}`); 43 | console.log(`[ASYNC] Created: ${job.created_at}, Total: ${job.total}, Processed: ${job.total.processed}`); 44 | console.log(`[ASYNC] Valid: ${job.total.valid}, Invalid: ${job.total.invalid}, Catchall: ${job.total.catchall}, Disposable: ${job.total.disposable}, Unknown: ${job.total.unknown}`); 45 | console.log('[ASYNC] ---'); 46 | }); 47 | } else { 48 | console.log('[ASYNC] No jobs found.'); 49 | } 50 | } catch (err) { 51 | if (err instanceof Error) { 52 | console.error('[ASYNC] Error searching jobs:', err.message); 53 | } else { 54 | console.error('[ASYNC] Unknown error occurred'); 55 | } 56 | } 57 | } 58 | 59 | // Run the async version with specific search parameters 60 | searchJobs({ page: 1, per_page: 5 }); 61 | -------------------------------------------------------------------------------- /examples/jobs-start.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { ApiResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Start job 14 | client.jobs.start(25999825) 15 | .then((resp: ApiResponse) => console.log('[THEN]', resp)) 16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 17 | 18 | // Alternative using async/await 19 | async function startJob(jobId: number, runSample?: boolean, allowManualReview?: boolean): Promise { 20 | try { 21 | const result = await client.jobs.start(jobId, runSample, allowManualReview); 22 | console.log('[ASYNC] Job started successfully!'); 23 | console.log('[ASYNC] Response:', result); 24 | } catch (err) { 25 | if (err instanceof Error) { 26 | console.error('[ASYNC] Error starting job:', err.message); 27 | } else { 28 | console.error('[ASYNC] Unknown error occurred'); 29 | } 30 | } 31 | } 32 | 33 | // Run the async version with a specific job ID 34 | startJob(25999824); 35 | 36 | -------------------------------------------------------------------------------- /examples/jobs-status.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { JobStatusResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Get a job's status 14 | client.jobs.status(25999836) 15 | .then((resp: JobStatusResponse) => console.log('[THEN]', resp)) 16 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 17 | 18 | // Alternative using async/await 19 | async function checkJobStatus(jobId: number): Promise { 20 | try { 21 | const status = await client.jobs.status(jobId); 22 | 23 | console.log('[ASYNC] Job status retrieved successfully!'); 24 | console.log(`[ASYNC] Job ID: ${status.id}`); 25 | console.log(`[ASYNC] Filename: ${status.filename}`); 26 | console.log(`[ASYNC] Status: ${status.job_status}`); 27 | console.log(`[ASYNC] Created: ${status.created_at}`); 28 | console.log(`[ASYNC] Started: ${status.started_at || 'Not started'}`); 29 | console.log(`[ASYNC] Finished: ${status.finished_at || 'Not finished'}`); 30 | console.log(`[ASYNC] Progress: ${status.percent_complete}%`); 31 | 32 | console.log('\n[ASYNC] Counts:'); 33 | console.log(`[ASYNC] Total: ${status.total}`); 34 | console.log(`[ASYNC] Processed: ${status.total.processed}`); 35 | console.log(`[ASYNC] Valid: ${status.total.valid}`); 36 | console.log(`[ASYNC] Invalid: ${status.total.invalid}`); 37 | console.log(`[ASYNC] Catchall: ${status.total.catchall}`); 38 | console.log(`[ASYNC] Disposable: ${status.total.disposable}`); 39 | console.log(`[ASYNC] Unknown: ${status.total.unknown}`); 40 | console.log(`[ASYNC] Duplicates: ${status.total.duplicates}`); 41 | 42 | if (status.failure_reason) { 43 | console.log(`\n[ASYNC] Failure reason: ${status.failure_reason}`); 44 | } 45 | } catch (err) { 46 | if (err instanceof Error) { 47 | console.error('[ASYNC] Error checking job status:', err.message); 48 | } else { 49 | console.error('[ASYNC] Unknown error occurred'); 50 | } 51 | } 52 | } 53 | 54 | // Run the async version with a specific job ID 55 | checkJobStatus(25999836); 56 | -------------------------------------------------------------------------------- /examples/poe-confirm.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import { POEConfirmationResponse } from '../src/types.js'; 3 | import dotenv from 'dotenv'; 4 | 5 | // Load environment variables from .local.env file 6 | dotenv.config(); 7 | 8 | // Initialize NeverBounce client with API key from environment variables 9 | const client = new NeverBounce({ 10 | apiKey: process.env.NEVERBOUNCE_API_KEY 11 | }); 12 | 13 | // Confirm POE (Proof of Email) 14 | client.poe.confirm( 15 | 'support@neverbounce.com', 16 | 0, // Valid result code (0 = valid) 17 | 'e3173fdbbdce6bad26522dae792911f2', // Confirmation token 18 | 'NBPOE-TXN-5942940c09669' // Transaction ID 19 | ) 20 | .then((resp: POEConfirmationResponse) => console.log('[THEN]', resp)) 21 | .catch((err: Error) => console.log('[THEN] ERROR: ' + err.message)); 22 | 23 | // Alternative using async/await 24 | async function confirmPOE( 25 | email: string, 26 | result: number, 27 | confirmationToken: string, 28 | transactionId: string 29 | ): Promise { 30 | try { 31 | const response = await client.poe.confirm(email, result, confirmationToken, transactionId); 32 | 33 | console.log('[ASYNC] POE confirmation successful!'); 34 | console.log(`[ASYNC] Token confirmed: ${response.token_confirmed}`); 35 | } catch (err) { 36 | if (err instanceof Error) { 37 | console.error('[ASYNC] Error confirming POE:', err.message); 38 | } else { 39 | console.error('[ASYNC] Unknown error occurred'); 40 | } 41 | } 42 | } 43 | 44 | // Run the async version with specific parameters 45 | confirmPOE( 46 | 'support@neverbounce.com', 47 | 0, // Valid result code (0 = valid) 48 | 'e3173fdbbdce6bad26522dae792911f2', // Confirmation token 49 | 'NBPOE-TXN-5942940c09669' // Transaction ID 50 | ); 51 | -------------------------------------------------------------------------------- /examples/single-check.ts: -------------------------------------------------------------------------------- 1 | import NeverBounce from '../src/NeverBounce.js'; 2 | import dotenv from 'dotenv'; 3 | 4 | // Load environment variables from .local.env file 5 | dotenv.config(); 6 | 7 | // Example of using Promise-based approach with the NeverBounce API 8 | const client = new NeverBounce({ 9 | apiKey: process.env.NEVERBOUNCE_API_KEY 10 | }); 11 | 12 | // Verify an email with address_info and credits_info 13 | client.single.check('support@neverbounce.com', true, true) 14 | .then(result => { 15 | console.log('[THEN] Result: ' + result.getResult()); 16 | console.log('[THEN] Result (numeric): ' + result.getNumericResult()); 17 | console.log('[THEN] Is Valid? ' + result.is(NeverBounce.result.valid)); 18 | 19 | // Access the response data with proper typing 20 | const response = result.getResponse(); 21 | 22 | if (response.credits_info) { 23 | console.log('[THEN] Free Credits Used: ' + response.credits_info.free_credits_used); 24 | console.log('[THEN] Paid Credits Used: ' + response.credits_info.paid_credits_used); 25 | } 26 | 27 | if (response.address_info) { 28 | console.log('[THEN] Host: ' + response.address_info.host); 29 | } 30 | }) 31 | .catch(err => { 32 | if (err instanceof Error) { 33 | console.log('[THEN] ERROR: ' + err.message); 34 | } else { 35 | console.log('[THEN] Unknown error occurred'); 36 | } 37 | }); 38 | 39 | // Example of using async/await with the NeverBounce API 40 | const runExample = async (): Promise => { 41 | // Initialize NeverBounce client with API key from environment variables 42 | const client = new NeverBounce({ 43 | apiKey: process.env.NEVERBOUNCE_API_KEY 44 | }); 45 | 46 | try { 47 | // Verify an email with address_info and credits_info 48 | const result = await client.single.check('support@neverbounce.com', true, true); 49 | 50 | console.log('[ASYNC] Result: ' + result.getResult()); 51 | console.log('[ASYNC] Result (numeric): ' + result.getNumericResult()); 52 | console.log('[ASYNC] Is Valid? ' + result.is(NeverBounce.result.valid)); 53 | 54 | // Access the response data with proper typing 55 | const response = result.getResponse(); 56 | 57 | if (response.credits_info) { 58 | console.log('[ASYNC] Free Credits Used: ' + response.credits_info.free_credits_used); 59 | console.log('[ASYNC] Paid Credits Used: ' + response.credits_info.paid_credits_used); 60 | } 61 | 62 | if (response.address_info) { 63 | console.log('[ASYNC] Host: ' + response.address_info.host); 64 | } 65 | } catch (err) { 66 | // Type guard to check if it's our custom error type 67 | if (err instanceof Error) { 68 | console.log('[ASYNC] ERROR: ' + err.message); 69 | } else { 70 | console.log('[ASYNC] Unknown error occurred'); 71 | } 72 | } 73 | }; 74 | 75 | // Run the example 76 | runExample().catch(err => { 77 | console.error('[ASYNC] Unhandled error:', err); 78 | }); 79 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | extensionsToTreatAsEsm: ['.ts'], 6 | moduleNameMapper: { 7 | '^(\\.{1,2}/.*)\\.js$': '$1', 8 | }, 9 | transform: { 10 | '^.+\\.tsx?$': [ 11 | 'ts-jest', 12 | { 13 | useESM: true, 14 | }, 15 | ], 16 | }, 17 | testMatch: ['**/tests/**/*.test.ts'], 18 | collectCoverage: true, 19 | coverageDirectory: 'coverage', 20 | collectCoverageFrom: [ 21 | 'src/**/*.ts', 22 | '!src/types.ts', 23 | '!src/index.ts', 24 | ], 25 | setupFilesAfterEnv: ['./tests/setup.ts'], 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neverbounce", 3 | "version": "5.0.5", 4 | "description": "An API wrapper for the NeverBounce API", 5 | "engines": { 6 | "node": ">=18.0.0" 7 | }, 8 | "type": "module", 9 | "main": "dist/cjs/src/index.js", 10 | "module": "dist/esm/src/index.js", 11 | "types": "dist/esm/src/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/esm/src/index.js", 15 | "require": "./dist/cjs/src/index.js", 16 | "types": "./dist/esm/src/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "dist/esm", 21 | "dist/cjs" 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/NeverBounce/NeverBounceApi-NodeJS" 26 | }, 27 | "scripts": { 28 | "build": "npm run build:esm && npm run build:cjs && npm run build:package", 29 | "build:esm": "tsc -p tsconfig.json", 30 | "build:cjs": "tsc -p tsconfig.cjs.json", 31 | "build:package": "mkdir -p dist/cjs && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && cp 'package.json' 'README.md' dist/", 32 | "lint": "eslint src/**/*.ts", 33 | "lint:fix": "eslint src/**/*.ts --fix", 34 | "prepublishOnly": "npm run build", 35 | "example": "tsx", 36 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest --config=jest.config.cjs", 37 | "test:watch": "jest --watch" 38 | }, 39 | "keywords": [ 40 | "email", 41 | "email verification", 42 | "email validation", 43 | "email cleaning", 44 | "verification", 45 | "validation", 46 | "cleaning" 47 | ], 48 | "author": "NeverBounce (https://neverbounce.com)", 49 | "contributors": [ 50 | "Mike Mollick ", 51 | "Omri Katz " 52 | ], 53 | "homepage": "https://neverbounce.com", 54 | "license": "MIT", 55 | "devDependencies": { 56 | "@types/jest": "^29.5.14", 57 | "@types/node": "^20.17.41", 58 | "@typescript-eslint/eslint-plugin": "^7.0.0", 59 | "@typescript-eslint/parser": "^7.0.0", 60 | "chai": "^4.3.10", 61 | "eslint": "^8.56.0", 62 | "jest": "29.7.0", 63 | "nock": "^13.4.0", 64 | "ts-jest": "29.1.1", 65 | "ts-node": "^10.9.2", 66 | "typescript": "^5.4.5" 67 | }, 68 | "dependencies": { 69 | "dotenv": "^16.5.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Account.ts: -------------------------------------------------------------------------------- 1 | import HttpsClient from './HttpsClient.js'; 2 | import { AccountInfoResponse } from './types.js'; 3 | 4 | /** 5 | * Account API endpoints 6 | */ 7 | class Account extends HttpsClient { 8 | /** 9 | * Returns account info 10 | * @returns Promise with account information 11 | */ 12 | async info(): Promise { 13 | return this.request({ 14 | method: 'GET', 15 | path: 'account/info' 16 | }); 17 | } 18 | } 19 | 20 | export default Account; 21 | -------------------------------------------------------------------------------- /src/Errors.ts: -------------------------------------------------------------------------------- 1 | // Add Node.js specific Error interface extension 2 | declare global { 3 | interface ErrorConstructor { 4 | captureStackTrace?(targetObject: object, constructorOpt?: new (...args: any[]) => any): void; 5 | } 6 | } 7 | 8 | /** 9 | * NeverBounce API Error class 10 | */ 11 | class _Error extends Error { 12 | public name: string; 13 | public type: string; 14 | public message: string; 15 | 16 | /** 17 | * Error constructor 18 | * @param type Error type 19 | * @param message Error message 20 | */ 21 | constructor(type?: string, message?: string) { 22 | super(message || 'General Error'); 23 | this.name = 'NeverBounce Error'; 24 | this.type = type || 'GeneralError'; 25 | this.message = message || 'General Error'; 26 | 27 | // Maintains proper stack trace for where our error was thrown (only available on V8) 28 | if (Error.captureStackTrace) { 29 | Error.captureStackTrace(this, _Error); 30 | } 31 | } 32 | 33 | /** 34 | * Error type constants 35 | */ 36 | static readonly AuthError: string = 'AuthError'; 37 | static readonly BadReferrerError: string = 'BadReferrerError'; 38 | static readonly GeneralError: string = 'GeneralError'; // Fallback error 39 | static readonly ThrottleError: string = 'ThrottleError'; 40 | 41 | /** 42 | * Error lookup table 43 | */ 44 | static readonly _lut: Record = { 45 | 'general_failure': _Error.GeneralError, // Fallback error 46 | 'auth_failure': _Error.AuthError, 47 | 'bad_referrer': _Error.BadReferrerError, 48 | 'throttle_triggered': _Error.ThrottleError, 49 | }; 50 | } 51 | 52 | export default _Error; 53 | -------------------------------------------------------------------------------- /src/HttpsClient.ts: -------------------------------------------------------------------------------- 1 | import _Error from './Errors.js'; 2 | import { ApiResponse, RequestOptions } from './types.js'; 3 | 4 | /** 5 | * Interface for NeverBounce client 6 | */ 7 | interface NeverBounceClient { 8 | getConfig(): { 9 | apiKey: string | null; 10 | apiVersion: string; 11 | timeout: number; 12 | opts: RequestOptions; 13 | }; 14 | getRequestOpts(params: Partial): RequestOptions; 15 | } 16 | 17 | /** 18 | * HTTP Client for making API requests using fetch 19 | */ 20 | class HttpsClient { 21 | private _nb: NeverBounceClient; 22 | private _version: string; 23 | 24 | /** 25 | * Constructor 26 | * @param _nb NeverBounce client instance 27 | */ 28 | constructor(_nb: NeverBounceClient) { 29 | this._nb = _nb; 30 | // Get version from package.json 31 | this._version = '5.0.0'; // This will be replaced with dynamic import when we implement the build process 32 | } 33 | 34 | /** 35 | * Performs API requests using fetch 36 | * @param params Request parameters 37 | * @param data Request data 38 | * @returns Promise with API response 39 | */ 40 | async request(params: Partial, data: Record = {}): Promise { 41 | const config = this._nb.getConfig(); 42 | // Set key 43 | data.key = config.apiKey; 44 | 45 | // Get request options 46 | const opts = this._nb.getRequestOpts(params); 47 | const path = opts.path ? `/${config.apiVersion}/${opts.path}` : ''; 48 | 49 | // Build URL 50 | const url = new URL(`https://${opts.host}:${opts.port}${path}`); 51 | 52 | // Set up fetch options 53 | const fetchOptions: RequestInit = { 54 | method: 'POST', 55 | headers: { 56 | 'Content-Type': 'application/json', 57 | 'Accept': opts.acceptedType, 58 | 'User-Agent': `NeverBounceApi-NodeJS/${this._version}` 59 | }, 60 | body: JSON.stringify(data) 61 | }; 62 | 63 | // Set up AbortController for timeout 64 | const controller = new AbortController(); 65 | if (config.timeout) { 66 | fetchOptions.signal = controller.signal; 67 | } 68 | 69 | try { 70 | const response = await fetch(url.toString(), fetchOptions); 71 | 72 | // Handle HTTP error codes 73 | if (response.status >= 400 && response.status < 500) { 74 | throw new _Error( 75 | _Error.GeneralError, 76 | `We were unable to complete your request. The following information was supplied: \n\n(Request error [status ${response.status}])` 77 | ); 78 | } 79 | 80 | if (response.status >= 500) { 81 | throw new _Error( 82 | _Error.GeneralError, 83 | `We were unable to complete your request. The following information was supplied: \n\n(Internal error [status ${response.status}])` 84 | ); 85 | } 86 | 87 | // Check content type 88 | const contentType = response.headers.get('content-type'); 89 | 90 | if (contentType?.includes('application/json')) { 91 | const decoded = await response.json() as T; 92 | 93 | // Check for missing status and error messages 94 | if (decoded.status === undefined || (decoded.status !== 'success' && decoded.message === undefined)) { 95 | throw new _Error( 96 | _Error.GeneralError, 97 | 'The response from server is incomplete. Either a status code was not included or ' + 98 | 'an error was returned without an error message. Try the request again, if ' + 99 | 'this error persists let us know at support@neverbounce.com.' + 100 | `\n\n(Internal error [status ${response.status}])` 101 | ); 102 | } 103 | 104 | // Handle error statuses 105 | if (decoded.status !== 'success') { 106 | const errorType = _Error._lut[decoded.status] || _Error.GeneralError; 107 | 108 | if (errorType === _Error.AuthError) { 109 | throw new _Error( 110 | _Error.AuthError, 111 | `We were unable to authenticate your request. The following information was supplied: ${decoded.message}\n\n(auth_failure)` 112 | ); 113 | } else { 114 | throw new _Error( 115 | errorType, 116 | `We were unable to complete your request. The following information was supplied: ${decoded.message}\n\n(${decoded.status})` 117 | ); 118 | } 119 | } 120 | 121 | return decoded; 122 | } else if (contentType && contentType !== opts.acceptedType) { 123 | throw new _Error( 124 | _Error.GeneralError, 125 | `The response from NeverBounce was returned with the type "${contentType}" but a response ` + 126 | `type of "${opts.acceptedType}" was expected. Try the request again, if this error persists ` + 127 | 'let us know at support@neverbounce.com.\n\n(Internal error)' 128 | ); 129 | } 130 | 131 | // Return text response for non-JSON content types 132 | const textResponse = await response.text(); 133 | return textResponse as unknown as T; 134 | 135 | } catch (error) { 136 | // Handle fetch errors or our custom errors 137 | if (error instanceof _Error) { 138 | throw error; 139 | } 140 | 141 | // Handle timeout or network errors 142 | if (error instanceof Error) { 143 | if (error.name === 'AbortError') { 144 | throw new _Error(_Error.GeneralError, 'The request timed out. Please try again later.'); 145 | } 146 | throw new _Error(_Error.GeneralError, `Network error: ${error.message}`); 147 | } 148 | 149 | // Fallback for unknown errors 150 | throw new _Error(_Error.GeneralError, 'An unknown error occurred during the request.'); 151 | } 152 | } 153 | } 154 | 155 | export default HttpsClient; 156 | -------------------------------------------------------------------------------- /src/Jobs.ts: -------------------------------------------------------------------------------- 1 | import HttpsClient from './HttpsClient.js'; 2 | import { 3 | ApiResponse, 4 | JobCreationRequest, 5 | JobCreationResponse, 6 | JobDeleteResponse, 7 | JobDownloadRequest, 8 | JobResultsRequest, 9 | JobResultsResponse, 10 | JobSearchRequest, 11 | JobSearchResponse, 12 | JobStatusResponse 13 | } from './types.js'; 14 | 15 | /** 16 | * Jobs API endpoints 17 | */ 18 | class Jobs extends HttpsClient { 19 | /** 20 | * Search for jobs 21 | * @param query Search query parameters 22 | * @returns Promise with search results 23 | */ 24 | async search(query?: JobSearchRequest): Promise { 25 | return this.request({ 26 | method: 'GET', 27 | path: 'jobs/search' 28 | }, query || {}); 29 | } 30 | 31 | /** 32 | * Creates a job 33 | * @param input Input data (array of emails or remote URL) 34 | * @param inputlocation Input location type ('remote_url' or 'supplied') 35 | * @param filename Filename for the job 36 | * @param runsample Whether to run a sample 37 | * @param autoparse Whether to automatically parse 38 | * @param autostart Whether to automatically start 39 | * @param historicalData Whether to leverage historical data 40 | * @param allowManualReview Whether to allow manual review 41 | * @param callbackUrl URL to call when job completes 42 | * @param callbackHeaders Headers to include in callback request 43 | * @returns Promise with job creation response 44 | */ 45 | async create( 46 | input: any[] | string, 47 | inputlocation?: string, 48 | filename?: string, 49 | runsample?: boolean, 50 | autoparse?: boolean, 51 | autostart?: boolean, 52 | historicalData?: boolean, 53 | allowManualReview?: boolean, 54 | callbackUrl?: string, 55 | callbackHeaders?: Record 56 | ): Promise { 57 | const data: JobCreationRequest & { 58 | request_meta_data?: { leverage_historical_data: number }; 59 | allow_manual_review?: boolean | null; 60 | callback_url?: string | null; 61 | callback_headers?: Record | null; 62 | } = { 63 | input: Array.isArray(input) ? input : [input], 64 | input_location: inputlocation, 65 | filename, 66 | run_sample: runsample || null, 67 | auto_start: autostart || null, 68 | auto_parse: autoparse || null, 69 | allow_manual_review: allowManualReview || null, 70 | callback_url: callbackUrl || null, 71 | callback_headers: callbackHeaders || null 72 | }; 73 | 74 | if (historicalData !== undefined) { 75 | data.request_meta_data = { leverage_historical_data: historicalData ? 1 : 0 }; 76 | } 77 | 78 | return this.request({ 79 | method: 'POST', 80 | path: 'jobs/create' 81 | }, data); 82 | } 83 | 84 | /** 85 | * Starts parsing job after creation 86 | * @param jobid Job ID 87 | * @param autostart Whether to automatically start 88 | * @returns Promise with parse response 89 | */ 90 | async parse(jobid: number, autostart?: boolean): Promise { 91 | return this.request({ 92 | method: 'POST', 93 | path: 'jobs/parse' 94 | }, { 95 | job_id: jobid, 96 | auto_start: autostart || undefined 97 | }); 98 | } 99 | 100 | /** 101 | * Starts job waiting to be started 102 | * @param jobid Job ID 103 | * @param runsample Whether to run a sample 104 | * @param allowManualReview Whether to allow manual review 105 | * @returns Promise with start response 106 | */ 107 | async start(jobid: number, runsample?: boolean, allowManualReview?: boolean): Promise { 108 | return this.request({ 109 | method: 'POST', 110 | path: 'jobs/start' 111 | }, { 112 | job_id: jobid, 113 | run_sample: runsample || undefined, 114 | allow_manual_review: allowManualReview || undefined 115 | }); 116 | } 117 | 118 | /** 119 | * Gets job status 120 | * @param jobid Job ID 121 | * @returns Promise with job status 122 | */ 123 | async status(jobid: number): Promise { 124 | return this.request({ 125 | method: 'GET', 126 | path: 'jobs/status' 127 | }, { 128 | job_id: jobid 129 | }); 130 | } 131 | 132 | /** 133 | * Retrieves job results 134 | * @param jobid Job ID 135 | * @param query Additional query parameters 136 | * @returns Promise with job results 137 | */ 138 | async results(jobid: number, query?: Omit): Promise { 139 | return this.request({ 140 | method: 'GET', 141 | path: 'jobs/results' 142 | }, Object.assign({ job_id: jobid }, query || {})); 143 | } 144 | 145 | /** 146 | * Downloads results as CSV 147 | * @param jobid Job ID 148 | * @param query Additional query parameters 149 | * @returns Promise with CSV data 150 | */ 151 | async download(jobid: number, query?: Omit): Promise { 152 | return this.request({ 153 | acceptedType: 'application/octet-stream', 154 | method: 'GET', 155 | path: 'jobs/download' 156 | }, Object.assign({ job_id: jobid }, query || {})); 157 | } 158 | 159 | /** 160 | * Deletes a job 161 | * @param jobid Job ID 162 | * @returns Promise with delete response 163 | */ 164 | async delete(jobid: number): Promise { 165 | return this.request({ 166 | method: 'POST', 167 | path: 'jobs/delete' 168 | }, { 169 | job_id: jobid 170 | }); 171 | } 172 | 173 | /** 174 | * Job input type constants 175 | */ 176 | static readonly remote: string = 'remote_url'; 177 | static readonly supplied: string = 'supplied'; 178 | 179 | /** 180 | * Helper object for job types and statuses 181 | * @since 4.1.4 182 | */ 183 | static readonly helpers = { 184 | inputType: { 185 | remote: Jobs.remote, 186 | supplied: Jobs.supplied 187 | }, 188 | status: { 189 | under_review: 'under_review', 190 | queued: 'queued', 191 | failed: 'failed', 192 | complete: 'complete', 193 | running: 'running', 194 | parsing: 'parsing', 195 | waiting: 'waiting', 196 | waiting_analyzed: 'waiting_analyzed', 197 | uploading: 'uploading' 198 | } 199 | }; 200 | } 201 | 202 | export default Jobs; 203 | -------------------------------------------------------------------------------- /src/NeverBounce.ts: -------------------------------------------------------------------------------- 1 | import { NeverBounceConfig, RequestOptions } from './types.js'; 2 | 3 | // Import resource classes 4 | import Account from './Account.js'; 5 | import Jobs from './Jobs.js'; 6 | import POE from './POE.js'; 7 | import Single from './Single.js'; 8 | import VerificationObject from './VerificationObject.js'; 9 | import _Error from './Errors.js'; 10 | 11 | /** 12 | * Main NeverBounce API client class 13 | */ 14 | class NeverBounce { 15 | /** 16 | * Resource instances 17 | */ 18 | public account: Account; 19 | public jobs: Jobs; 20 | public poe: POE; 21 | public single: Single; 22 | 23 | /** 24 | * Default config values 25 | */ 26 | static readonly defaultConfig: NeverBounceConfig = { 27 | apiVersion: 'v4.2', 28 | apiKey: null, 29 | timeout: 30000, 30 | opts: { 31 | acceptedType: 'application/json', 32 | host: 'api.neverbounce.com', 33 | port: 443, 34 | headers: { 35 | 'Content-Type': 'application/json', 36 | 'User-Agent': 'NeverBounce-Node/5.0.0' 37 | } 38 | } 39 | }; 40 | 41 | /** 42 | * Verification result helpers 43 | */ 44 | static readonly result = VerificationObject.helpers; 45 | 46 | /** 47 | * Job helpers 48 | */ 49 | static readonly job = Jobs.helpers; 50 | 51 | /** 52 | * Error types 53 | */ 54 | static readonly errors = _Error; 55 | 56 | /** 57 | * Configuration 58 | */ 59 | private config: NeverBounceConfig; 60 | 61 | /** 62 | * Initializes NeverBounce object 63 | * @param config Configuration options 64 | */ 65 | constructor(config: Partial = {}) { 66 | // Create config by merging defaults with user config 67 | this.config = { 68 | ...structuredClone(NeverBounce.defaultConfig), 69 | ...config, 70 | opts: { 71 | ...NeverBounce.defaultConfig.opts, 72 | ...(config.opts || {}), 73 | headers: { 74 | ...NeverBounce.defaultConfig.opts.headers, 75 | ...(config.opts?.headers || {}) 76 | } 77 | } 78 | }; 79 | 80 | // Initialize resources 81 | this.account = new Account(this); 82 | this.jobs = new Jobs(this); 83 | this.poe = new POE(this); 84 | this.single = new Single(this); 85 | } 86 | 87 | /** 88 | * Returns config 89 | */ 90 | getConfig(): NeverBounceConfig { 91 | return this.config; 92 | } 93 | 94 | /** 95 | * Make request options 96 | */ 97 | getRequestOpts(opts: Partial = {}): RequestOptions { 98 | return { 99 | ...this.config.opts, 100 | ...opts, 101 | headers: { 102 | ...this.config.opts.headers, 103 | ...(opts.headers || {}) 104 | } 105 | }; 106 | } 107 | 108 | /** 109 | * Update configuration 110 | */ 111 | updateConfig(config: Partial): void { 112 | if (config.apiKey) this.config.apiKey = config.apiKey; 113 | if (config.apiVersion) this.config.apiVersion = config.apiVersion; 114 | if (config.timeout) this.config.timeout = config.timeout; 115 | 116 | if (config.opts) { 117 | this.config.opts = { 118 | ...this.config.opts, 119 | ...config.opts, 120 | headers: { 121 | ...this.config.opts.headers, 122 | ...(config.opts.headers || {}) 123 | } 124 | }; 125 | } 126 | } 127 | 128 | /** 129 | * Set api key (convenience method) 130 | * @param key API key 131 | */ 132 | setApiKey(key: string): void { 133 | this.config.apiKey = key; 134 | } 135 | 136 | /** 137 | * Set api host (convenience method) 138 | * @param host API host 139 | */ 140 | setHost(host: string): void { 141 | this.config.opts.host = host; 142 | } 143 | } 144 | 145 | export default NeverBounce; 146 | -------------------------------------------------------------------------------- /src/POE.ts: -------------------------------------------------------------------------------- 1 | import HttpsClient from './HttpsClient.js'; 2 | import { POEConfirmationRequest, POEConfirmationResponse } from './types.js'; 3 | 4 | /** 5 | * POE (Proof of Email) API endpoints 6 | */ 7 | class POE extends HttpsClient { 8 | /** 9 | * Confirms a POE token 10 | * @param email Email address 11 | * @param result Verification result 12 | * @param confirmationToken Confirmation token 13 | * @param transactionId Transaction ID 14 | * @returns Promise with confirmation response 15 | */ 16 | async confirm( 17 | email: string, 18 | result: number, 19 | confirmationToken: string, 20 | transactionId: string 21 | ): Promise { 22 | const data: POEConfirmationRequest = { 23 | email, 24 | result, 25 | confirmation_token: confirmationToken, 26 | transaction_id: transactionId 27 | }; 28 | 29 | return this.request({ 30 | method: 'POST', 31 | path: 'poe/confirm' 32 | }, data); 33 | } 34 | } 35 | 36 | export default POE; 37 | -------------------------------------------------------------------------------- /src/Single.ts: -------------------------------------------------------------------------------- 1 | import HttpsClient from './HttpsClient.js'; 2 | import VerificationObject from './VerificationObject.js'; 3 | import { SingleVerificationRequest, SingleVerificationResponse } from './types.js'; 4 | 5 | /** 6 | * Single email verification API endpoints 7 | */ 8 | class Single extends HttpsClient { 9 | /** 10 | * Performs the verification of a single email 11 | * @param email Email address to verify 12 | * @param address_info Whether to include address info in the response 13 | * @param credits_info Whether to include credits info in the response 14 | * @param timeout Timeout for the verification in milliseconds 15 | * @param historicalData Whether to leverage historical data 16 | * @returns Promise with verification object 17 | */ 18 | async check( 19 | email: string, 20 | address_info?: boolean, 21 | credits_info?: boolean, 22 | timeout?: number, 23 | historicalData?: boolean 24 | ): Promise { 25 | const data: SingleVerificationRequest & { request_meta_data?: { leverage_historical_data: number } } = { 26 | email, 27 | address_info: address_info || undefined, 28 | credits_info: credits_info || undefined, 29 | timeout: timeout || undefined 30 | }; 31 | 32 | if (historicalData !== undefined) { 33 | data.request_meta_data = { leverage_historical_data: historicalData ? 1 : 0 }; 34 | } 35 | 36 | const response = await this.request({ 37 | method: 'GET', 38 | path: 'single/check' 39 | }, data); 40 | 41 | return new VerificationObject(response); 42 | } 43 | } 44 | 45 | export default Single; 46 | -------------------------------------------------------------------------------- /src/VerificationObject.ts: -------------------------------------------------------------------------------- 1 | import { SingleVerificationResponse, VerificationFlags } from './types.js'; 2 | 3 | /** 4 | * Verification Object class for handling verification results 5 | */ 6 | class VerificationObject { 7 | private response: SingleVerificationResponse; 8 | 9 | /** 10 | * Constructor 11 | * @param response Verification response from API 12 | */ 13 | constructor(response: SingleVerificationResponse) { 14 | this.response = response; 15 | } 16 | 17 | /** 18 | * Get the full response object 19 | * @returns The verification response 20 | */ 21 | getResponse(): SingleVerificationResponse { 22 | return this.response; 23 | } 24 | 25 | /** 26 | * Returns result 27 | * @returns The verification result 28 | */ 29 | getResult(): string | number { 30 | return this.response.result; 31 | } 32 | 33 | /** 34 | * Returns text code 35 | * @returns The numeric result code 36 | */ 37 | getNumericResult(): number | string { 38 | return (VerificationObject as any)[this.getResult()]; 39 | } 40 | 41 | /** 42 | * Returns true if result is of specified types 43 | * @param codes Verification result codes to check 44 | * @returns Whether the result matches any of the specified codes 45 | */ 46 | is(codes: string | number | Array): boolean { 47 | if (Array.isArray(codes)) { 48 | return (codes.indexOf(this.getResult() as any) > -1 || codes.indexOf(this.getNumericResult() as any) > -1); 49 | } else { 50 | return (codes === this.getResult() || codes === this.getNumericResult()); 51 | } 52 | } 53 | 54 | /** 55 | * Returns true if result is NOT of specified types 56 | * @param codes Verification result codes to check 57 | * @returns Whether the result does not match any of the specified codes 58 | */ 59 | not(codes: string | number | Array): boolean { 60 | if (Array.isArray(codes)) { 61 | return (codes.indexOf(this.getResult() as any) === -1 && codes.indexOf(this.getNumericResult() as any) === -1); 62 | } else { 63 | return (codes !== this.getResult() && codes !== this.getNumericResult()); 64 | } 65 | } 66 | 67 | /** 68 | * Returns true if the flag was returned 69 | * @param flag Flag to check 70 | * @returns Whether the flag is present 71 | */ 72 | hasFlag(flag: string): boolean { 73 | return this.response 74 | && this.response.flags 75 | && this.response.flags.indexOf(flag) > -1; 76 | } 77 | 78 | /** 79 | * Result code constants 80 | */ 81 | static readonly valid: number = 0; 82 | static readonly invalid: number = 1; 83 | static readonly disposable: number = 2; 84 | static readonly catchall: number = 3; 85 | static readonly unknown: number = 4; 86 | 87 | /** 88 | * Helper object for result codes and flags 89 | * @since 4.1.4 90 | */ 91 | static readonly helpers: { 92 | [key: string]: any; 93 | [key: number]: string; 94 | valid: number; 95 | invalid: number; 96 | disposable: number; 97 | catchall: number; 98 | unknown: number; 99 | flags: VerificationFlags; 100 | } = { 101 | // Numerically indexed 102 | [VerificationObject.valid]: 'valid', 103 | [VerificationObject.invalid]: 'invalid', 104 | [VerificationObject.disposable]: 'disposable', 105 | [VerificationObject.catchall]: 'catchall', 106 | [VerificationObject.unknown]: 'unknown', 107 | 108 | // Text indexed 109 | valid: VerificationObject.valid, 110 | invalid: VerificationObject.invalid, 111 | disposable: VerificationObject.disposable, 112 | catchall: VerificationObject.catchall, 113 | unknown: VerificationObject.unknown, 114 | 115 | flags: { 116 | has_dns: 'has_dns', 117 | has_dns_mx: 'has_dns_mx', 118 | bad_syntax: 'bad_syntax', 119 | free_email_host: 'free_email_host', 120 | profanity: 'profanity', 121 | role_account: 'role_account', 122 | disposable_email: 'disposable_email', 123 | government_host: 'government_host', 124 | academic_host: 'academic_host', 125 | military_host: 'military_host', 126 | international_host: 'international_host', 127 | squatter_host: 'squatter_host', 128 | spelling_mistake: 'spelling_mistake', 129 | bad_dns: 'bad_dns', 130 | temporary_dns_error: 'temporary_dns_error', 131 | connect_fails: 'connect_fails', 132 | accepts_all: 'accepts_all', 133 | contains_alias: 'contains_alias', 134 | contains_subdomain: 'contains_subdomain', 135 | smtp_connectable: 'smtp_connectable', 136 | spamtrap_network: 'spamtrap_network', 137 | } 138 | }; 139 | 140 | /** 141 | * @deprecated 4.1.4 Will be removed in next minor release 142 | */ 143 | static readonly numericCodes: Record = { 144 | valid: VerificationObject.valid, 145 | invalid: VerificationObject.invalid, 146 | disposable: VerificationObject.disposable, 147 | catchall: VerificationObject.catchall, 148 | unknown: VerificationObject.unknown, 149 | }; 150 | } 151 | 152 | export default VerificationObject; 153 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * NeverBounce API Node.js Client 3 | * @module neverbounce 4 | */ 5 | 6 | import NeverBounce from './NeverBounce.js'; 7 | 8 | export default NeverBounce; 9 | export { default as VerificationObject } from './VerificationObject.js'; 10 | export { default as _Error } from './Errors.js'; 11 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Common type definitions for the NeverBounce API 3 | */ 4 | 5 | /** 6 | * NeverBounce API Configuration 7 | */ 8 | export interface NeverBounceConfig { 9 | apiVersion: string; 10 | apiKey: string | null; 11 | timeout: number; 12 | opts: RequestOptions; 13 | } 14 | 15 | /** 16 | * HTTP Request Options 17 | */ 18 | export interface RequestOptions { 19 | acceptedType: string; 20 | host: string; 21 | port: number; 22 | headers: Record; 23 | path?: string; 24 | method?: string; 25 | } 26 | 27 | /** 28 | * API Response 29 | */ 30 | export interface ApiResponse { 31 | status: string; 32 | message?: string; 33 | execution_time?: number; 34 | [key: string]: any; 35 | } 36 | 37 | /** 38 | * Verification Result Codes 39 | */ 40 | export enum VerificationResult { 41 | Valid = 0, 42 | Invalid = 1, 43 | Disposable = 2, 44 | Catchall = 3, 45 | Unknown = 4 46 | } 47 | 48 | /** 49 | * Verification Flags 50 | */ 51 | export interface VerificationFlags { 52 | has_dns: string; 53 | has_dns_mx: string; 54 | bad_syntax: string; 55 | free_email_host: string; 56 | profanity: string; 57 | role_account: string; 58 | disposable_email: string; 59 | government_host: string; 60 | academic_host: string; 61 | military_host: string; 62 | international_host: string; 63 | squatter_host: string; 64 | spelling_mistake: string; 65 | bad_dns: string; 66 | temporary_dns_error: string; 67 | connect_fails: string; 68 | accepts_all: string; 69 | contains_alias: string; 70 | contains_subdomain: string; 71 | smtp_connectable: string; 72 | spamtrap_network: string; 73 | } 74 | 75 | /** 76 | * Job Input Types 77 | */ 78 | export enum JobInputType { 79 | RemoteUrl = 'remote_url', 80 | Supplied = 'supplied' 81 | } 82 | 83 | /** 84 | * Job Status Types 85 | */ 86 | export enum JobStatus { 87 | UnderReview = 'under_review', 88 | Queued = 'queued', 89 | Failed = 'failed', 90 | Complete = 'complete', 91 | Running = 'running', 92 | Parsing = 'parsing', 93 | Waiting = 'waiting', 94 | WaitingAnalyzed = 'waiting_analyzed', 95 | Uploading = 'uploading' 96 | } 97 | 98 | /** 99 | * Error Types 100 | */ 101 | export enum ErrorType { 102 | AuthError = 'auth_failure', 103 | BadReferrerError = 'bad_referrer', 104 | GeneralError = 'general_failure', 105 | ThrottleError = 'throttle_triggered' 106 | } 107 | 108 | /** 109 | * Single Verification Request 110 | */ 111 | export interface SingleVerificationRequest { 112 | email: string; 113 | address_info?: boolean; 114 | credits_info?: boolean; 115 | timeout?: number; 116 | } 117 | 118 | /** 119 | * Single Verification Response 120 | */ 121 | export interface SingleVerificationResponse extends ApiResponse { 122 | result: number; 123 | flags: string[]; 124 | suggested_correction?: string; 125 | retry_token?: string; 126 | address_info?: { 127 | original: string; 128 | normalized: string; 129 | addr: string; 130 | alias: string; 131 | host: string; 132 | fqdn: string; 133 | domain: string; 134 | subdomain: string; 135 | tld: string; 136 | }; 137 | credits_info?: { 138 | paid_credits_used: number; 139 | free_credits_used: number; 140 | paid_credits_remaining: number; 141 | free_credits_remaining: number; 142 | }; 143 | } 144 | 145 | /** 146 | * Account Info Response 147 | */ 148 | export interface AccountInfoResponse extends ApiResponse { 149 | billing_type: string; 150 | credits: { 151 | paid_credits_used: number; 152 | free_credits_used: number; 153 | paid_credits_remaining: number; 154 | free_credits_remaining: number; 155 | }; 156 | job_counts: { 157 | completed: number; 158 | processing: number; 159 | queued: number; 160 | under_review: number; 161 | }; 162 | } 163 | 164 | /** 165 | * POE Confirmation Request 166 | */ 167 | export interface POEConfirmationRequest { 168 | email: string; 169 | transaction_id: string; 170 | confirmation_token: string; 171 | result: number; 172 | } 173 | 174 | /** 175 | * POE Confirmation Response 176 | */ 177 | export interface POEConfirmationResponse extends ApiResponse { 178 | token_confirmed: boolean; 179 | token_age: number; 180 | } 181 | 182 | /** 183 | * Job Creation Request 184 | */ 185 | export interface JobCreationRequest { 186 | input: string[]; 187 | input_location?: string; 188 | auto_parse?: boolean | null; 189 | auto_start?: boolean | null; 190 | run_sample?: boolean | null; 191 | filename?: string; 192 | } 193 | 194 | /** 195 | * Job Creation Response 196 | */ 197 | export interface JobCreationResponse extends ApiResponse { 198 | job_id: number; 199 | } 200 | 201 | /** 202 | * Job Status Response 203 | */ 204 | export interface JobStatusResponse extends ApiResponse { 205 | id: number; 206 | filename: string; 207 | created_at: string; 208 | started_at: string | null; 209 | finished_at: string | null; 210 | total: { 211 | processed: number; 212 | valid: number; 213 | invalid: number; 214 | catchall: number; 215 | disposable: number; 216 | unknown: number; 217 | duplicates: number; 218 | bad_syntax: number; 219 | }; 220 | job_status: string; 221 | bounce_estimate: number | null; 222 | percent_complete: number; 223 | failure_reason: string | null; 224 | } 225 | 226 | /** 227 | * Job Results Request 228 | */ 229 | export interface JobResultsRequest { 230 | job_id: number; 231 | page?: number; 232 | per_page?: number; 233 | include_verification_time?: boolean; 234 | } 235 | 236 | /** 237 | * Job Results Response 238 | */ 239 | export interface JobResultsResponse extends ApiResponse { 240 | total_results: number; 241 | total_pages: number; 242 | query: { 243 | job_id: number; 244 | valids: boolean; 245 | invalids: boolean; 246 | catchalls: boolean; 247 | disposables: boolean; 248 | unknowns: boolean; 249 | page: number; 250 | items_per_page: number; 251 | }; 252 | results: Array<{ 253 | data: Record; 254 | verification: { 255 | result: number; 256 | flags: string[]; 257 | suggested_correction: string | null; 258 | address_info: { 259 | original: string; 260 | normalized: string; 261 | addr: string; 262 | alias: string; 263 | host: string; 264 | fqdn: string; 265 | domain: string; 266 | subdomain: string; 267 | tld: string; 268 | }; 269 | verification_time?: number; 270 | }; 271 | }>; 272 | } 273 | 274 | /** 275 | * Job Search Request 276 | */ 277 | export interface JobSearchRequest { 278 | job_id?: number; 279 | page?: number; 280 | per_page?: number; 281 | } 282 | 283 | /** 284 | * Job Search Response 285 | */ 286 | export interface JobSearchResponse extends ApiResponse { 287 | total_results: number; 288 | total_pages: number; 289 | query: { 290 | page: number; 291 | items_per_page: number; 292 | }; 293 | results: Array<{ 294 | id: number; 295 | job_status: string; 296 | filename: string; 297 | created_at: string; 298 | started_at: string | null; 299 | finished_at: string | null; 300 | total: { 301 | processed: number; 302 | valid: number; 303 | invalid: number; 304 | catchall: number; 305 | disposable: number; 306 | unknown: number; 307 | duplicates: number; 308 | bad_syntax: number; 309 | }; 310 | bounce_estimate: number | null; 311 | percent_complete: number; 312 | failure_reason: string | null; 313 | }>; 314 | } 315 | 316 | /** 317 | * Job Delete Response 318 | */ 319 | export interface JobDeleteResponse extends ApiResponse { 320 | job_id: number; 321 | } 322 | 323 | /** 324 | * Job Download Request 325 | */ 326 | export interface JobDownloadRequest { 327 | job_id: number; 328 | } 329 | 330 | /** 331 | * Resource Class Constructor 332 | */ 333 | export interface ResourceConstructor { 334 | new (client: any): any; 335 | } 336 | 337 | /** 338 | * Resources Map 339 | */ 340 | export interface ResourcesMap { 341 | [key: string]: ResourceConstructor; 342 | } 343 | -------------------------------------------------------------------------------- /test-cjs.cjs: -------------------------------------------------------------------------------- 1 | // CommonJS test 2 | const NeverBounce = require('./dist/cjs/src/index.js'); 3 | 4 | console.log('CommonJS require successful!'); 5 | console.log('NeverBounce version:', NeverBounce.default); 6 | -------------------------------------------------------------------------------- /test-esm.js: -------------------------------------------------------------------------------- 1 | // ESM test 2 | import NeverBounce from './dist/esm/src/index.js'; 3 | 4 | console.log('ESM import successful!'); 5 | console.log('NeverBounce:', NeverBounce); 6 | -------------------------------------------------------------------------------- /tests/Account.test.ts: -------------------------------------------------------------------------------- 1 | import Account from '../src/Account'; 2 | import { RequestOptions } from '../src/types.js'; 3 | import { jest } from '@jest/globals'; 4 | 5 | // We don't need to mock HttpsClient directly 6 | // Instead, we'll mock the request method on the Account instance 7 | 8 | describe('Account', () => { 9 | let account: any; 10 | let mockClient: any; 11 | 12 | beforeEach(() => { 13 | // Clear all mock implementations and calls before each test 14 | jest.clearAllMocks(); 15 | 16 | // Create a mock NeverBounce client 17 | mockClient = { 18 | getConfig: jest.fn().mockReturnValue({ 19 | apiKey: 'test-api-key', 20 | apiVersion: 'v4.2', 21 | timeout: 30000, 22 | opts: { 23 | acceptedType: 'application/json', 24 | host: 'api.neverbounce.com', 25 | port: 443, 26 | headers: { 27 | 'Content-Type': 'application/json', 28 | 'User-Agent': 'NeverBounce-Node/5.0.0' 29 | } 30 | } 31 | }), 32 | getRequestOpts: jest.fn().mockImplementation((params: any) => { 33 | return { 34 | acceptedType: 'application/json', 35 | host: 'api.neverbounce.com', 36 | port: 443, 37 | headers: { 38 | 'Content-Type': 'application/json', 39 | 'User-Agent': 'NeverBounce-Node/5.0.0', 40 | ...((params && params.headers) || {}) 41 | }, 42 | ...(params || {}) 43 | } as RequestOptions; 44 | }) 45 | }; 46 | 47 | // Create a new Account instance for each test 48 | account = new Account(mockClient); 49 | 50 | // Mock the request method directly on the Account instance 51 | account.request = jest.fn(); 52 | }); 53 | 54 | describe('info()', () => { 55 | it('should call request with correct parameters', async () => { 56 | // Arrange 57 | const mockResponse = { 58 | id: '12345', 59 | name: 'Test Account', 60 | status: 'active', 61 | createdAt: '2023-01-01T00:00:00Z' 62 | }; 63 | 64 | account.request.mockResolvedValueOnce(mockResponse); 65 | 66 | // Act 67 | const result = await account.info(); 68 | 69 | // Assert 70 | expect(account.request).toHaveBeenCalledTimes(1); 71 | expect(account.request).toHaveBeenCalledWith({ 72 | method: 'GET', 73 | path: 'account/info' 74 | }); 75 | expect(result).toEqual(mockResponse); 76 | }); 77 | 78 | it('should propagate errors from the request method', async () => { 79 | // Arrange 80 | const mockError = new Error('Network error'); 81 | account.request.mockRejectedValueOnce(mockError); 82 | 83 | // Act & Assert 84 | await expect(account.info()).rejects.toThrow('Network error'); 85 | expect(account.request).toHaveBeenCalledTimes(1); 86 | }); 87 | 88 | it('should handle empty response correctly', async () => { 89 | // Arrange 90 | const mockEmptyResponse = {}; 91 | account.request.mockResolvedValueOnce(mockEmptyResponse); 92 | 93 | // Act 94 | const result = await account.info(); 95 | 96 | // Assert 97 | expect(result).toEqual({}); 98 | expect(account.request).toHaveBeenCalledTimes(1); 99 | }); 100 | 101 | it('should return the exact response from request method', async () => { 102 | // Arrange 103 | const mockDetailedResponse = { 104 | id: 'acc_123456789', 105 | name: 'Premium Account', 106 | status: 'active', 107 | features: ['feature1', 'feature2'], 108 | limits: { 109 | maxUsers: 10, 110 | maxStorage: '5GB' 111 | }, 112 | createdAt: '2023-01-01T00:00:00Z', 113 | updatedAt: '2023-06-15T12:34:56Z' 114 | }; 115 | 116 | account.request.mockResolvedValueOnce(mockDetailedResponse); 117 | 118 | // Act 119 | const result = await account.info(); 120 | 121 | // Assert 122 | expect(result).toEqual(mockDetailedResponse); 123 | expect(account.request).toHaveBeenCalledWith({ 124 | method: 'GET', 125 | path: 'account/info' 126 | }); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /tests/Errors.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { describe, it, expect } from '@jest/globals'; 4 | import _Error from '../src/Errors.js'; 5 | 6 | describe('_Error', () => { 7 | describe('constructor', () => { 8 | it('should create an error with default values when no parameters are provided', () => { 9 | const error = new _Error(); 10 | 11 | expect(error.name).toBe('NeverBounce Error'); 12 | expect(error.type).toBe('GeneralError'); 13 | expect(error.message).toBe('General Error'); 14 | expect(error instanceof Error).toBe(true); 15 | }); 16 | 17 | it('should create an error with the provided type and default message', () => { 18 | const error = new _Error('AuthError'); 19 | 20 | expect(error.name).toBe('NeverBounce Error'); 21 | expect(error.type).toBe('AuthError'); 22 | expect(error.message).toBe('General Error'); 23 | }); 24 | 25 | it('should create an error with the provided type and message', () => { 26 | const error = new _Error('AuthError', 'Invalid API key'); 27 | 28 | expect(error.name).toBe('NeverBounce Error'); 29 | expect(error.type).toBe('AuthError'); 30 | expect(error.message).toBe('Invalid API key'); 31 | }); 32 | }); 33 | 34 | describe('static properties', () => { 35 | it('should expose error type constants', () => { 36 | expect(_Error.AuthError).toBe('AuthError'); 37 | expect(_Error.BadReferrerError).toBe('BadReferrerError'); 38 | expect(_Error.GeneralError).toBe('GeneralError'); 39 | expect(_Error.ThrottleError).toBe('ThrottleError'); 40 | }); 41 | 42 | it('should have a lookup table for error types', () => { 43 | expect(_Error._lut['auth_failure']).toBe(_Error.AuthError); 44 | expect(_Error._lut['bad_referrer']).toBe(_Error.BadReferrerError); 45 | expect(_Error._lut['general_failure']).toBe(_Error.GeneralError); 46 | expect(_Error._lut['throttle_triggered']).toBe(_Error.ThrottleError); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/Jobs.test.ts: -------------------------------------------------------------------------------- 1 | import Jobs from '../src/Jobs.js'; 2 | import { RequestOptions } from '../src/types.js'; 3 | import { jest } from '@jest/globals'; 4 | 5 | // We don't need to mock HttpsClient directly 6 | // Instead, we'll mock the request method on the Jobs instance 7 | 8 | describe('Jobs', () => { 9 | let jobs: any; 10 | let mockClient: any; 11 | 12 | beforeEach(() => { 13 | // Clear all mocks 14 | jest.clearAllMocks(); 15 | 16 | // Create a mock NeverBounce client 17 | mockClient = { 18 | getConfig: jest.fn().mockReturnValue({ 19 | apiKey: 'test-api-key', 20 | apiVersion: 'v4.2', 21 | timeout: 30000, 22 | opts: { 23 | acceptedType: 'application/json', 24 | host: 'api.neverbounce.com', 25 | port: 443, 26 | headers: { 27 | 'Content-Type': 'application/json', 28 | 'User-Agent': 'NeverBounce-Node/5.0.0' 29 | } 30 | } 31 | }), 32 | getRequestOpts: jest.fn().mockImplementation((params: any) => { 33 | return { 34 | acceptedType: 'application/json', 35 | host: 'api.neverbounce.com', 36 | port: 443, 37 | headers: { 38 | 'Content-Type': 'application/json', 39 | 'User-Agent': 'NeverBounce-Node/5.0.0', 40 | ...((params && params.headers) || {}) 41 | }, 42 | ...(params || {}) 43 | } as RequestOptions; 44 | }) 45 | }; 46 | 47 | // Create new Jobs instance 48 | jobs = new Jobs(mockClient); 49 | 50 | // Mock the request method directly on the Jobs instance 51 | jobs.request = jest.fn(); 52 | }); 53 | 54 | describe('search', () => { 55 | it('should call request with correct parameters when no query is provided', async () => { 56 | // Arrange 57 | const mockResponse = { status: 'success', results: [] }; 58 | jobs.request.mockResolvedValueOnce(mockResponse); 59 | 60 | // Act 61 | const result = await jobs.search(); 62 | 63 | // Assert 64 | expect(jobs.request).toHaveBeenCalledWith( 65 | { method: 'GET', path: 'jobs/search' }, 66 | {} 67 | ); 68 | expect(result).toBe(mockResponse); 69 | }); 70 | 71 | it('should call request with correct parameters when query is provided', async () => { 72 | // Arrange 73 | const mockQuery = { page: 1, per_page: 10 }; 74 | const mockResponse = { status: 'success', results: [] }; 75 | jobs.request.mockResolvedValueOnce(mockResponse); 76 | 77 | // Act 78 | const result = await jobs.search(mockQuery); 79 | 80 | // Assert 81 | expect(jobs.request).toHaveBeenCalledWith( 82 | { method: 'GET', path: 'jobs/search' }, 83 | mockQuery 84 | ); 85 | expect(result).toBe(mockResponse); 86 | }); 87 | }); 88 | 89 | describe('create', () => { 90 | it('should handle array input correctly', async () => { 91 | // Arrange 92 | const mockInput = ['test@example.com', 'test2@example.com']; 93 | const mockResponse = { status: 'success', job_id: 123 }; 94 | jobs.request.mockResolvedValueOnce(mockResponse); 95 | 96 | // Act 97 | const result = await jobs.create(mockInput); 98 | 99 | // Assert 100 | expect(jobs.request).toHaveBeenCalledWith( 101 | { method: 'POST', path: 'jobs/create' }, 102 | { 103 | input: mockInput, 104 | input_location: undefined, 105 | filename: undefined, 106 | run_sample: null, 107 | auto_start: null, 108 | auto_parse: null, 109 | allow_manual_review: null, 110 | callback_url: null, 111 | callback_headers: null 112 | } 113 | ); 114 | expect(result).toBe(mockResponse); 115 | }); 116 | 117 | it('should handle string input correctly', async () => { 118 | // Arrange 119 | const mockInput = 'https://example.com/emails.csv'; 120 | const mockResponse = { status: 'success', job_id: 123 }; 121 | jobs.request.mockResolvedValueOnce(mockResponse); 122 | 123 | // Act 124 | const result = await jobs.create(mockInput, Jobs.remote); 125 | 126 | // Assert 127 | expect(jobs.request).toHaveBeenCalledWith( 128 | { method: 'POST', path: 'jobs/create' }, 129 | { 130 | input: [mockInput], 131 | input_location: Jobs.remote, 132 | filename: undefined, 133 | run_sample: null, 134 | auto_start: null, 135 | auto_parse: null, 136 | allow_manual_review: null, 137 | callback_url: null, 138 | callback_headers: null 139 | } 140 | ); 141 | expect(result).toBe(mockResponse); 142 | }); 143 | 144 | it('should handle all optional parameters correctly', async () => { 145 | // Arrange 146 | const mockInput = ['test@example.com']; 147 | const mockFilename = 'test-job.csv'; 148 | const mockCallbackHeaders = { 'X-Custom-Header': 'value' }; 149 | const mockCallbackUrl = 'https://example.com/callback'; 150 | const mockResponse = { status: 'success', job_id: 123 }; 151 | jobs.request.mockResolvedValueOnce(mockResponse); 152 | 153 | // Act 154 | const result = await jobs.create( 155 | mockInput, 156 | Jobs.supplied, 157 | mockFilename, 158 | true, // runsample 159 | true, // autoparse 160 | true, // autostart 161 | true, // historicalData 162 | true, // allowManualReview 163 | mockCallbackUrl, 164 | mockCallbackHeaders 165 | ); 166 | 167 | // Assert 168 | expect(jobs.request).toHaveBeenCalledWith( 169 | { method: 'POST', path: 'jobs/create' }, 170 | { 171 | input: mockInput, 172 | input_location: Jobs.supplied, 173 | filename: mockFilename, 174 | run_sample: true, 175 | auto_start: true, 176 | auto_parse: true, 177 | request_meta_data: { leverage_historical_data: 1 }, 178 | allow_manual_review: true, 179 | callback_url: mockCallbackUrl, 180 | callback_headers: mockCallbackHeaders 181 | } 182 | ); 183 | expect(result).toBe(mockResponse); 184 | }); 185 | 186 | it('should set historical data to 0 when false', async () => { 187 | // Arrange 188 | const mockInput = ['test@example.com']; 189 | const mockResponse = { status: 'success', job_id: 123 }; 190 | jobs.request.mockResolvedValueOnce(mockResponse); 191 | 192 | // Act 193 | const result = await jobs.create( 194 | mockInput, 195 | undefined, 196 | undefined, 197 | undefined, 198 | undefined, 199 | undefined, 200 | false // historicalData 201 | ); 202 | 203 | // Assert 204 | expect(jobs.request).toHaveBeenCalledWith( 205 | { method: 'POST', path: 'jobs/create' }, 206 | expect.objectContaining({ 207 | request_meta_data: { leverage_historical_data: 0 } 208 | }) 209 | ); 210 | expect(result).toBe(mockResponse); 211 | }); 212 | }); 213 | 214 | describe('parse', () => { 215 | it('should call request with correct parameters', async () => { 216 | // Arrange 217 | const jobId = 123; 218 | const mockResponse = { status: 'success' }; 219 | jobs.request.mockResolvedValueOnce(mockResponse); 220 | 221 | // Act 222 | const result = await jobs.parse(jobId); 223 | 224 | // Assert 225 | expect(jobs.request).toHaveBeenCalledWith( 226 | { method: 'POST', path: 'jobs/parse' }, 227 | { job_id: jobId, auto_start: undefined } 228 | ); 229 | expect(result).toBe(mockResponse); 230 | }); 231 | 232 | it('should include autostart when provided', async () => { 233 | // Arrange 234 | const jobId = 123; 235 | const autostart = true; 236 | const mockResponse = { status: 'success' }; 237 | jobs.request.mockResolvedValueOnce(mockResponse); 238 | 239 | // Act 240 | const result = await jobs.parse(jobId, autostart); 241 | 242 | // Assert 243 | expect(jobs.request).toHaveBeenCalledWith( 244 | { method: 'POST', path: 'jobs/parse' }, 245 | { job_id: jobId, auto_start: autostart } 246 | ); 247 | expect(result).toBe(mockResponse); 248 | }); 249 | }); 250 | 251 | describe('start', () => { 252 | it('should call request with correct parameters', async () => { 253 | // Arrange 254 | const jobId = 123; 255 | const mockResponse = { status: 'success' }; 256 | jobs.request.mockResolvedValueOnce(mockResponse); 257 | 258 | // Act 259 | const result = await jobs.start(jobId); 260 | 261 | // Assert 262 | expect(jobs.request).toHaveBeenCalledWith( 263 | { method: 'POST', path: 'jobs/start' }, 264 | { job_id: jobId, run_sample: undefined, allow_manual_review: undefined } 265 | ); 266 | expect(result).toBe(mockResponse); 267 | }); 268 | 269 | it('should include optional parameters when provided', async () => { 270 | // Arrange 271 | const jobId = 123; 272 | const runSample = true; 273 | const allowManualReview = true; 274 | const mockResponse = { status: 'success' }; 275 | jobs.request.mockResolvedValueOnce(mockResponse); 276 | 277 | // Act 278 | const result = await jobs.start(jobId, runSample, allowManualReview); 279 | 280 | // Assert 281 | expect(jobs.request).toHaveBeenCalledWith( 282 | { method: 'POST', path: 'jobs/start' }, 283 | { job_id: jobId, run_sample: runSample, allow_manual_review: allowManualReview } 284 | ); 285 | expect(result).toBe(mockResponse); 286 | }); 287 | }); 288 | 289 | describe('status', () => { 290 | it('should call request with correct parameters', async () => { 291 | // Arrange 292 | const jobId = 123; 293 | const mockResponse = { 294 | status: 'success', 295 | job_status: 'complete', 296 | total_records: 100 297 | }; 298 | jobs.request.mockResolvedValueOnce(mockResponse); 299 | 300 | // Act 301 | const result = await jobs.status(jobId); 302 | 303 | // Assert 304 | expect(jobs.request).toHaveBeenCalledWith( 305 | { method: 'GET', path: 'jobs/status' }, 306 | { job_id: jobId } 307 | ); 308 | expect(result).toBe(mockResponse); 309 | }); 310 | }); 311 | 312 | describe('results', () => { 313 | it('should call request with correct parameters when no query is provided', async () => { 314 | // Arrange 315 | const jobId = 123; 316 | const mockResponse = { 317 | status: 'success', 318 | results: [] 319 | }; 320 | jobs.request.mockResolvedValueOnce(mockResponse); 321 | 322 | // Act 323 | const result = await jobs.results(jobId); 324 | 325 | // Assert 326 | expect(jobs.request).toHaveBeenCalledWith( 327 | { method: 'GET', path: 'jobs/results' }, 328 | { job_id: jobId } 329 | ); 330 | expect(result).toBe(mockResponse); 331 | }); 332 | 333 | it('should call request with correct parameters when query is provided', async () => { 334 | // Arrange 335 | const jobId = 123; 336 | const query = { page: 1, per_page: 10 }; 337 | const mockResponse = { 338 | status: 'success', 339 | results: [] 340 | }; 341 | jobs.request.mockResolvedValueOnce(mockResponse); 342 | 343 | // Act 344 | const result = await jobs.results(jobId, query); 345 | 346 | // Assert 347 | expect(jobs.request).toHaveBeenCalledWith( 348 | { method: 'GET', path: 'jobs/results' }, 349 | { job_id: jobId, ...query } 350 | ); 351 | expect(result).toBe(mockResponse); 352 | }); 353 | }); 354 | 355 | describe('download', () => { 356 | it('should call request with correct parameters when no query is provided', async () => { 357 | // Arrange 358 | const jobId = 123; 359 | const mockResponse = 'email,result\ntest@example.com,valid'; 360 | jobs.request.mockResolvedValueOnce(mockResponse); 361 | 362 | // Act 363 | const result = await jobs.download(jobId); 364 | 365 | // Assert 366 | expect(jobs.request).toHaveBeenCalledWith( 367 | { 368 | acceptedType: 'application/octet-stream', 369 | method: 'GET', 370 | path: 'jobs/download' 371 | }, 372 | { job_id: jobId } 373 | ); 374 | expect(result).toBe(mockResponse); 375 | }); 376 | 377 | it('should call request with correct parameters when query is provided', async () => { 378 | // Arrange 379 | const jobId = 123; 380 | const query = { bounce_flags: true }; 381 | const mockResponse = 'email,result,bounce_flags\ntest@example.com,valid,none'; 382 | jobs.request.mockResolvedValueOnce(mockResponse); 383 | 384 | // Act 385 | const result = await jobs.download(jobId, query); 386 | 387 | // Assert 388 | expect(jobs.request).toHaveBeenCalledWith( 389 | { 390 | acceptedType: 'application/octet-stream', 391 | method: 'GET', 392 | path: 'jobs/download' 393 | }, 394 | { job_id: jobId, ...query } 395 | ); 396 | expect(result).toBe(mockResponse); 397 | }); 398 | }); 399 | 400 | describe('delete', () => { 401 | it('should call request with correct parameters', async () => { 402 | // Arrange 403 | const jobId = 123; 404 | const mockResponse = { status: 'success' }; 405 | jobs.request.mockResolvedValueOnce(mockResponse); 406 | 407 | // Act 408 | const result = await jobs.delete(jobId); 409 | 410 | // Assert 411 | expect(jobs.request).toHaveBeenCalledWith( 412 | { method: 'POST', path: 'jobs/delete' }, 413 | { job_id: jobId } 414 | ); 415 | expect(result).toBe(mockResponse); 416 | }); 417 | }); 418 | 419 | describe('Static properties', () => { 420 | it('should have correct input type constants', () => { 421 | expect(Jobs.remote).toBe('remote_url'); 422 | expect(Jobs.supplied).toBe('supplied'); 423 | }); 424 | 425 | it('should have correct helpers object', () => { 426 | expect(Jobs.helpers.inputType.remote).toBe('remote_url'); 427 | expect(Jobs.helpers.inputType.supplied).toBe('supplied'); 428 | expect(Jobs.helpers.status.under_review).toBe('under_review'); 429 | expect(Jobs.helpers.status.queued).toBe('queued'); 430 | expect(Jobs.helpers.status.failed).toBe('failed'); 431 | expect(Jobs.helpers.status.complete).toBe('complete'); 432 | expect(Jobs.helpers.status.running).toBe('running'); 433 | expect(Jobs.helpers.status.parsing).toBe('parsing'); 434 | expect(Jobs.helpers.status.waiting).toBe('waiting'); 435 | expect(Jobs.helpers.status.waiting_analyzed).toBe('waiting_analyzed'); 436 | expect(Jobs.helpers.status.uploading).toBe('uploading'); 437 | }); 438 | }); 439 | }); 440 | -------------------------------------------------------------------------------- /tests/NeverBounce.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { describe, it, expect } from '@jest/globals'; 4 | import NeverBounce from '../src/NeverBounce.js'; 5 | import Account from '../src/Account.js'; 6 | import Jobs from '../src/Jobs.js'; 7 | import POE from '../src/POE.js'; 8 | import Single from '../src/Single.js'; 9 | import { NeverBounceConfig } from '../src/types.js'; 10 | 11 | describe('NeverBounce', () => { 12 | describe('constructor', () => { 13 | it('should initialize with default config when no config is provided', () => { 14 | const nb = new NeverBounce(); 15 | const config = nb.getConfig(); 16 | 17 | expect(config.apiVersion).toBe('v4.2'); 18 | expect(config.apiKey).toBeNull(); 19 | expect(config.timeout).toBe(30000); 20 | expect(config.opts.host).toBe('api.neverbounce.com'); 21 | expect(config.opts.port).toBe(443); 22 | expect(config.opts.headers['Content-Type']).toBe('application/json'); 23 | expect(config.opts.headers['User-Agent']).toBe('NeverBounce-Node/5.0.0'); 24 | }); 25 | 26 | it('should merge provided config with default config', () => { 27 | const customConfig: Partial = { 28 | apiKey: 'test-api-key', 29 | timeout: 60000, 30 | opts: { 31 | acceptedType: 'application/json', 32 | host: 'custom.api.neverbounce.com', 33 | port: 443, 34 | headers: { 35 | 'Custom-Header': 'custom-value' 36 | } 37 | } 38 | }; 39 | 40 | const nb = new NeverBounce(customConfig); 41 | const config = nb.getConfig(); 42 | 43 | expect(config.apiKey).toBe('test-api-key'); 44 | expect(config.timeout).toBe(60000); 45 | expect(config.opts.host).toBe('custom.api.neverbounce.com'); 46 | expect(config.opts.port).toBe(443); // Default value 47 | expect(config.opts.headers['Content-Type']).toBe('application/json'); // Default value 48 | expect(config.opts.headers['Custom-Header']).toBe('custom-value'); // Custom value 49 | }); 50 | 51 | it('should initialize resource instances', () => { 52 | const nb = new NeverBounce(); 53 | 54 | expect(nb.account).toBeInstanceOf(Account); 55 | expect(nb.jobs).toBeInstanceOf(Jobs); 56 | expect(nb.poe).toBeInstanceOf(POE); 57 | expect(nb.single).toBeInstanceOf(Single); 58 | }); 59 | }); 60 | 61 | describe('getRequestOpts', () => { 62 | it('should return request options with merged headers', () => { 63 | const nb = new NeverBounce({ 64 | opts: { 65 | acceptedType: 'application/json', 66 | host: 'api.neverbounce.com', 67 | port: 443, 68 | headers: { 69 | 'Default-Header': 'default-value' 70 | } 71 | } 72 | }); 73 | 74 | const opts = nb.getRequestOpts({ 75 | method: 'POST', 76 | path: 'test/path', 77 | headers: { 78 | 'Custom-Header': 'custom-value' 79 | } 80 | }); 81 | 82 | expect(opts.method).toBe('POST'); 83 | expect(opts.path).toBe('test/path'); 84 | expect(opts.headers['Default-Header']).toBe('default-value'); 85 | expect(opts.headers['Custom-Header']).toBe('custom-value'); 86 | expect(opts.headers['Content-Type']).toBe('application/json'); // From default config 87 | }); 88 | }); 89 | 90 | describe('updateConfig', () => { 91 | it('should update config values', () => { 92 | const nb = new NeverBounce(); 93 | 94 | nb.updateConfig({ 95 | apiKey: 'updated-api-key', 96 | apiVersion: 'v5.0', 97 | timeout: 45000, 98 | opts: { 99 | acceptedType: 'application/json', 100 | host: 'updated.api.neverbounce.com', 101 | port: 443, 102 | headers: { 103 | 'Updated-Header': 'updated-value' 104 | } 105 | } 106 | }); 107 | 108 | const config = nb.getConfig(); 109 | 110 | expect(config.apiKey).toBe('updated-api-key'); 111 | expect(config.apiVersion).toBe('v5.0'); 112 | expect(config.timeout).toBe(45000); 113 | expect(config.opts.host).toBe('updated.api.neverbounce.com'); 114 | expect(config.opts.headers['Updated-Header']).toBe('updated-value'); 115 | expect(config.opts.headers['Content-Type']).toBe('application/json'); // Preserved from default 116 | }); 117 | 118 | it('should only update provided config values', () => { 119 | const nb = new NeverBounce({ 120 | apiKey: 'initial-api-key', 121 | timeout: 30000 122 | }); 123 | 124 | nb.updateConfig({ 125 | timeout: 45000 126 | }); 127 | 128 | const config = nb.getConfig(); 129 | 130 | expect(config.apiKey).toBe('initial-api-key'); // Unchanged 131 | expect(config.timeout).toBe(45000); // Updated 132 | }); 133 | }); 134 | 135 | describe('setApiKey', () => { 136 | it('should update the API key', () => { 137 | const nb = new NeverBounce(); 138 | 139 | nb.setApiKey('new-api-key'); 140 | 141 | expect(nb.getConfig().apiKey).toBe('new-api-key'); 142 | }); 143 | }); 144 | 145 | describe('setHost', () => { 146 | it('should update the API host', () => { 147 | const nb = new NeverBounce(); 148 | 149 | nb.setHost('new.api.neverbounce.com'); 150 | 151 | expect(nb.getConfig().opts.host).toBe('new.api.neverbounce.com'); 152 | }); 153 | }); 154 | 155 | describe('static properties', () => { 156 | it('should expose result helpers', () => { 157 | expect(NeverBounce.result.valid).toBe(0); 158 | expect(NeverBounce.result.invalid).toBe(1); 159 | expect(NeverBounce.result.disposable).toBe(2); 160 | expect(NeverBounce.result.catchall).toBe(3); 161 | expect(NeverBounce.result.unknown).toBe(4); 162 | }); 163 | 164 | it('should expose job helpers', () => { 165 | expect(NeverBounce.job.inputType.remote).toBe('remote_url'); 166 | expect(NeverBounce.job.inputType.supplied).toBe('supplied'); 167 | expect(NeverBounce.job.status.complete).toBe('complete'); 168 | expect(NeverBounce.job.status.failed).toBe('failed'); 169 | expect(NeverBounce.job.status.queued).toBe('queued'); 170 | expect(NeverBounce.job.status.running).toBe('running'); 171 | expect(NeverBounce.job.status.parsing).toBe('parsing'); 172 | expect(NeverBounce.job.status.waiting).toBe('waiting'); 173 | expect(NeverBounce.job.status.waiting_analyzed).toBe('waiting_analyzed'); 174 | expect(NeverBounce.job.status.uploading).toBe('uploading'); 175 | expect(NeverBounce.job.status.under_review).toBe('under_review'); 176 | }); 177 | 178 | it('should expose error types', () => { 179 | expect(NeverBounce.errors.AuthError).toBe('AuthError'); 180 | expect(NeverBounce.errors.BadReferrerError).toBe('BadReferrerError'); 181 | expect(NeverBounce.errors.GeneralError).toBe('GeneralError'); 182 | expect(NeverBounce.errors.ThrottleError).toBe('ThrottleError'); 183 | }); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /tests/POE.test.ts: -------------------------------------------------------------------------------- 1 | import POE from '../src/POE.js'; 2 | import { RequestOptions } from '../src/types.js'; 3 | import { jest } from '@jest/globals'; 4 | 5 | // We don't need to mock HttpsClient directly 6 | // Instead, we'll mock the request method on the POE instance 7 | 8 | describe('POE', () => { 9 | let poe: any; 10 | let mockClient: any; 11 | 12 | beforeEach(() => { 13 | // Clear all mocks 14 | jest.clearAllMocks(); 15 | 16 | mockClient = { 17 | getConfig: jest.fn().mockReturnValue({ 18 | apiKey: 'test-api-key', 19 | apiVersion: 'v4.2', 20 | timeout: 30000, 21 | opts: { 22 | acceptedType: 'application/json', 23 | host: 'api.neverbounce.com', 24 | port: 443, 25 | headers: { 26 | 'Content-Type': 'application/json', 27 | 'User-Agent': 'NeverBounce-Node/5.0.0' 28 | } 29 | } 30 | }), 31 | getRequestOpts: jest.fn().mockImplementation((params: any) => { 32 | return { 33 | acceptedType: 'application/json', 34 | host: 'api.neverbounce.com', 35 | port: 443, 36 | headers: { 37 | 'Content-Type': 'application/json', 38 | 'User-Agent': 'NeverBounce-Node/5.0.0', 39 | ...((params && params.headers) || {}) 40 | }, 41 | ...(params || {}) 42 | } as RequestOptions; 43 | }) 44 | }; 45 | 46 | // Create a new POE instance for each test 47 | poe = new POE(mockClient); 48 | 49 | // Mock the request method directly on the POE instance 50 | poe.request = jest.fn(); 51 | }); 52 | 53 | describe('confirm', () => { 54 | it('should call request with correct parameters', async () => { 55 | // Arrange 56 | const email = 'test@example.com'; 57 | const result = 0; 58 | const confirmationToken = 'abc123'; 59 | const transactionId = 'txn456'; 60 | 61 | const mockResponse = { 62 | status: 'success', 63 | token_confirmed: true, 64 | token_age: '2023-12-01 12:34:56' 65 | }; 66 | 67 | poe.request.mockResolvedValueOnce(mockResponse); 68 | 69 | // Act 70 | const response = await poe.confirm(email, result, confirmationToken, transactionId); 71 | 72 | // Assert 73 | expect(poe.request).toHaveBeenCalledTimes(1); 74 | expect(poe.request).toHaveBeenCalledWith( 75 | { 76 | method: 'POST', 77 | path: 'poe/confirm' 78 | }, 79 | { 80 | email: email, 81 | result: result, 82 | confirmation_token: confirmationToken, 83 | transaction_id: transactionId 84 | } 85 | ); 86 | expect(response).toEqual(mockResponse); 87 | }); 88 | 89 | it('should handle different result values', async () => { 90 | // Arrange 91 | const email = 'test@example.com'; 92 | const result = 1; // Different result value 93 | const confirmationToken = 'abc123'; 94 | const transactionId = 'txn456'; 95 | 96 | const mockResponse = { 97 | status: 'success', 98 | token_confirmed: true, 99 | token_age: '2023-12-01 12:34:56' 100 | }; 101 | 102 | poe.request.mockResolvedValueOnce(mockResponse); 103 | 104 | // Act 105 | const response = await poe.confirm(email, result, confirmationToken, transactionId); 106 | 107 | // Assert 108 | expect(poe.request).toHaveBeenCalledWith( 109 | { 110 | method: 'POST', 111 | path: 'poe/confirm' 112 | }, 113 | { 114 | email: email, 115 | result: result, 116 | confirmation_token: confirmationToken, 117 | transaction_id: transactionId 118 | } 119 | ); 120 | expect(response).toEqual(mockResponse); 121 | }); 122 | 123 | it('should propagate errors from request method', async () => { 124 | // Arrange 125 | const email = 'test@example.com'; 126 | const result = 0; 127 | const confirmationToken = 'abc123'; 128 | const transactionId = 'txn456'; 129 | 130 | const mockError = new Error('API Error'); 131 | poe.request.mockRejectedValueOnce(mockError); 132 | 133 | // Act & Assert 134 | await expect(poe.confirm(email, result, confirmationToken, transactionId)) 135 | .rejects.toThrow('API Error'); 136 | 137 | expect(poe.request).toHaveBeenCalledTimes(1); 138 | }); 139 | 140 | it('should handle successful confirmation with false token_confirmed', async () => { 141 | // Arrange 142 | const email = 'test@example.com'; 143 | const result = 0; 144 | const confirmationToken = 'abc123'; 145 | const transactionId = 'txn456'; 146 | 147 | const mockResponse = { 148 | status: 'success', 149 | token_confirmed: false, 150 | message: 'Token has expired' 151 | }; 152 | 153 | poe.request.mockResolvedValueOnce(mockResponse); 154 | 155 | // Act 156 | const response = await poe.confirm(email, result, confirmationToken, transactionId); 157 | 158 | // Assert 159 | expect(poe.request).toHaveBeenCalledTimes(1); 160 | expect(response).toEqual(mockResponse); 161 | }); 162 | 163 | it('should handle confirmation with non-standard response fields', async () => { 164 | // Arrange 165 | const email = 'test@example.com'; 166 | const result = 2; 167 | const confirmationToken = 'abc123'; 168 | const transactionId = 'txn456'; 169 | 170 | const mockResponse = { 171 | status: 'success', 172 | token_confirmed: true, 173 | token_age: '2023-12-01 12:34:56', 174 | additional_field: 'extra data' 175 | }; 176 | 177 | poe.request.mockResolvedValueOnce(mockResponse); 178 | 179 | // Act 180 | const response = await poe.confirm(email, result, confirmationToken, transactionId); 181 | 182 | // Assert 183 | expect(poe.request).toHaveBeenCalledTimes(1); 184 | expect(response).toEqual(mockResponse); 185 | expect(response.additional_field).toBe('extra data'); 186 | }); 187 | 188 | it('should handle special characters in email address', async () => { 189 | // Arrange 190 | const email = 'test+special@example.com'; // Email with + character 191 | const result = 0; 192 | const confirmationToken = 'abc123'; 193 | const transactionId = 'txn456'; 194 | 195 | const mockResponse = { 196 | status: 'success', 197 | token_confirmed: true, 198 | token_age: '2023-12-01 12:34:56' 199 | }; 200 | 201 | poe.request.mockResolvedValueOnce(mockResponse); 202 | 203 | // Act 204 | const response = await poe.confirm(email, result, confirmationToken, transactionId); 205 | 206 | // Assert 207 | expect(poe.request).toHaveBeenCalledWith( 208 | { 209 | method: 'POST', 210 | path: 'poe/confirm' 211 | }, 212 | { 213 | email: email, 214 | result: result, 215 | confirmation_token: confirmationToken, 216 | transaction_id: transactionId 217 | } 218 | ); 219 | expect(response).toEqual(mockResponse); 220 | }); 221 | 222 | it('should handle long tokens and transaction IDs', async () => { 223 | // Arrange 224 | const email = 'test@example.com'; 225 | const result = 0; 226 | const confirmationToken = 'a'.repeat(100); // Very long token 227 | const transactionId = 'b'.repeat(100); // Very long transaction ID 228 | 229 | const mockResponse = { 230 | status: 'success', 231 | token_confirmed: true, 232 | token_age: '2023-12-01 12:34:56' 233 | }; 234 | 235 | poe.request.mockResolvedValueOnce(mockResponse); 236 | 237 | // Act 238 | const response = await poe.confirm(email, result, confirmationToken, transactionId); 239 | 240 | // Assert 241 | expect(poe.request).toHaveBeenCalledWith( 242 | { 243 | method: 'POST', 244 | path: 'poe/confirm' 245 | }, 246 | { 247 | email: email, 248 | result: result, 249 | confirmation_token: confirmationToken, 250 | transaction_id: transactionId 251 | } 252 | ); 253 | expect(response).toEqual(mockResponse); 254 | }); 255 | }); 256 | }); 257 | -------------------------------------------------------------------------------- /tests/Single.test.ts: -------------------------------------------------------------------------------- 1 | import Single from '../src/Single'; 2 | import { RequestOptions } from '../src/types'; 3 | import { jest } from '@jest/globals'; 4 | 5 | // We don't need to mock HttpsClient directly 6 | // Instead, we'll mock the request method on the Single instance 7 | 8 | describe('Single', () => { 9 | let single: any; 10 | let mockResponse: any; 11 | let mockClient: any; 12 | 13 | beforeEach(() => { 14 | // Clear all mocks before each test 15 | jest.clearAllMocks(); 16 | 17 | // Create a mock NeverBounce client 18 | mockClient = { 19 | getConfig: jest.fn().mockReturnValue({ 20 | apiKey: 'test-api-key', 21 | apiVersion: 'v4.2', 22 | timeout: 30000, 23 | opts: { 24 | acceptedType: 'application/json', 25 | host: 'api.neverbounce.com', 26 | port: 443, 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | 'User-Agent': 'NeverBounce-Node/5.0.0' 30 | } 31 | } 32 | }), 33 | getRequestOpts: jest.fn().mockImplementation((params: any) => { 34 | return { 35 | acceptedType: 'application/json', 36 | host: 'api.neverbounce.com', 37 | port: 443, 38 | headers: { 39 | 'Content-Type': 'application/json', 40 | 'User-Agent': 'NeverBounce-Node/5.0.0', 41 | ...((params && params.headers) || {}) 42 | }, 43 | ...(params || {}) 44 | } as RequestOptions; 45 | }) 46 | }; 47 | 48 | // Create an instance of Single 49 | single = new Single(mockClient); 50 | 51 | // Setup a mock response 52 | mockResponse = { 53 | email: 'test@example.com', 54 | status: 'valid', 55 | result: 'valid', 56 | // Add other properties as needed for your tests 57 | }; 58 | 59 | // Mock the request method directly on the Single instance 60 | single.request = jest.fn(); 61 | single.request.mockResolvedValue(mockResponse); 62 | }); 63 | 64 | describe('check method', () => { 65 | 66 | test('should include address_info when provided', async () => { 67 | const email = 'test@example.com'; 68 | const address_info = true; 69 | 70 | await single.check(email, address_info); 71 | 72 | expect(single.request).toHaveBeenCalledWith( 73 | { 74 | method: 'GET', 75 | path: 'single/check' 76 | }, 77 | { 78 | email: 'test@example.com', 79 | address_info: true, 80 | credits_info: undefined, 81 | timeout: undefined 82 | } 83 | ); 84 | }); 85 | 86 | test('should include credits_info when provided', async () => { 87 | const email = 'test@example.com'; 88 | const address_info = false; 89 | const credits_info = true; 90 | 91 | await single.check(email, address_info, credits_info); 92 | 93 | expect(single.request).toHaveBeenCalledWith( 94 | { 95 | method: 'GET', 96 | path: 'single/check' 97 | }, 98 | { 99 | email: 'test@example.com', 100 | address_info: undefined, 101 | credits_info: true, 102 | timeout: undefined 103 | } 104 | ); 105 | }); 106 | 107 | test('should include timeout when provided', async () => { 108 | const email = 'test@example.com'; 109 | const address_info = false; 110 | const credits_info = false; 111 | const timeout = 5000; 112 | 113 | await single.check(email, address_info, credits_info, timeout); 114 | 115 | expect(single.request).toHaveBeenCalledWith( 116 | { 117 | method: 'GET', 118 | path: 'single/check' 119 | }, 120 | { 121 | email: 'test@example.com', 122 | address_info: undefined, 123 | credits_info: undefined, 124 | timeout: 5000 125 | } 126 | ); 127 | }); 128 | 129 | test('should include historicalData when provided as true', async () => { 130 | const email = 'test@example.com'; 131 | const address_info = false; 132 | const credits_info = false; 133 | const timeout = null; 134 | const historicalData = true; 135 | 136 | await single.check(email, address_info, credits_info, timeout, historicalData); 137 | 138 | expect(single.request).toHaveBeenCalledWith( 139 | { 140 | method: 'GET', 141 | path: 'single/check' 142 | }, 143 | { 144 | email: 'test@example.com', 145 | address_info: undefined, 146 | credits_info: undefined, 147 | timeout: undefined, 148 | request_meta_data: { 149 | leverage_historical_data: 1 150 | } 151 | } 152 | ); 153 | }); 154 | 155 | test('should include historicalData when provided as false', async () => { 156 | const email = 'test@example.com'; 157 | const address_info = false; 158 | const credits_info = false; 159 | const timeout = null; 160 | const historicalData = false; 161 | 162 | await single.check(email, address_info, credits_info, timeout, historicalData); 163 | 164 | expect(single.request).toHaveBeenCalledWith( 165 | { 166 | method: 'GET', 167 | path: 'single/check' 168 | }, 169 | { 170 | email: 'test@example.com', 171 | address_info: undefined, 172 | credits_info: undefined, 173 | timeout: undefined, 174 | request_meta_data: { 175 | leverage_historical_data: 0 176 | } 177 | } 178 | ); 179 | }); 180 | 181 | test('should include all parameters when provided', async () => { 182 | const email = 'test@example.com'; 183 | const address_info = true; 184 | const credits_info = true; 185 | const timeout = 10000; 186 | const historicalData = true; 187 | 188 | await single.check(email, address_info, credits_info, timeout, historicalData); 189 | 190 | expect(single.request).toHaveBeenCalledWith( 191 | { 192 | method: 'GET', 193 | path: 'single/check' 194 | }, 195 | { 196 | email: 'test@example.com', 197 | address_info: true, 198 | credits_info: true, 199 | timeout: 10000, 200 | request_meta_data: { 201 | leverage_historical_data: 1 202 | } 203 | } 204 | ); 205 | }); 206 | 207 | 208 | }); 209 | }); 210 | -------------------------------------------------------------------------------- /tests/VerificationObject.test.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { describe, it, expect } from '@jest/globals'; 4 | import VerificationObject from '../src/VerificationObject.js'; 5 | import { SingleVerificationResponse } from '../src/types.js'; 6 | 7 | describe('VerificationObject', () => { 8 | // Sample verification response 9 | const validResponse: SingleVerificationResponse = { 10 | status: 'success', 11 | result: 0, 12 | flags: ['has_dns', 'has_dns_mx', 'smtp_connectable'], 13 | execution_time: 123, 14 | address_info: { 15 | original: 'test@example.com', 16 | normalized: 'test@example.com', 17 | addr: 'test', 18 | alias: '', 19 | host: 'example.com', 20 | fqdn: 'example.com', 21 | domain: 'example', 22 | subdomain: '', 23 | tld: 'com' 24 | }, 25 | credits_info: { 26 | paid_credits_used: 1, 27 | free_credits_used: 0, 28 | paid_credits_remaining: 999, 29 | free_credits_remaining: 0 30 | } 31 | }; 32 | 33 | // Sample invalid response for testing 34 | const invalidResponse: SingleVerificationResponse = { 35 | status: 'success', 36 | result: 1, 37 | flags: ['bad_syntax'], 38 | execution_time: 123 39 | }; 40 | 41 | // Use invalidResponse in a test to avoid unused variable warning 42 | it('should handle invalid responses', () => { 43 | const verificationObject = new VerificationObject(invalidResponse); 44 | expect(verificationObject.getResult()).toBe(1); 45 | expect(verificationObject.hasFlag('bad_syntax')).toBe(true); 46 | }); 47 | 48 | describe('constructor', () => { 49 | it('should initialize with the provided response', () => { 50 | const verificationObject = new VerificationObject(validResponse); 51 | expect(verificationObject.getResponse()).toBe(validResponse); 52 | }); 53 | }); 54 | 55 | describe('getResult', () => { 56 | it('should return the result from the response', () => { 57 | const verificationObject = new VerificationObject(validResponse); 58 | expect(verificationObject.getResult()).toBe(0); 59 | }); 60 | }); 61 | 62 | describe('getNumericResult', () => { 63 | it('should return the numeric result for a text result', () => { 64 | // Create a response with a text result 65 | const textResponse = { ...validResponse, result: 'valid' as unknown as number }; 66 | const verificationObject = new VerificationObject(textResponse); 67 | 68 | // Mock the static property access 69 | const originalValid = VerificationObject.valid; 70 | (VerificationObject as any).valid = 0; 71 | 72 | expect(verificationObject.getNumericResult()).toBe(0); 73 | 74 | // Restore the original value 75 | (VerificationObject as any).valid = originalValid; 76 | }); 77 | 78 | it('should return the text result for a numeric result', () => { 79 | const verificationObject = new VerificationObject(validResponse); 80 | 81 | // Mock the static property access 82 | const originalHelper = VerificationObject.helpers; 83 | (VerificationObject as any)[0] = 'valid'; 84 | 85 | expect(verificationObject.getNumericResult()).toBe('valid'); 86 | 87 | // Restore the original value 88 | (VerificationObject as any).helpers = originalHelper; 89 | }); 90 | }); 91 | 92 | describe('is', () => { 93 | it('should return true if the result matches the specified code', () => { 94 | const verificationObject = new VerificationObject(validResponse); 95 | expect(verificationObject.is(0)).toBe(true); 96 | expect(verificationObject.is('valid')).toBe(true); 97 | }); 98 | 99 | it('should return true if the result matches any of the specified codes', () => { 100 | const verificationObject = new VerificationObject(validResponse); 101 | expect(verificationObject.is([0, 1])).toBe(true); 102 | expect(verificationObject.is(['valid', 'invalid'])).toBe(true); 103 | }); 104 | 105 | it('should return false if the result does not match the specified code', () => { 106 | const verificationObject = new VerificationObject(validResponse); 107 | expect(verificationObject.is(1)).toBe(false); 108 | expect(verificationObject.is('invalid')).toBe(false); 109 | }); 110 | 111 | it('should return false if the result does not match any of the specified codes', () => { 112 | const verificationObject = new VerificationObject(validResponse); 113 | expect(verificationObject.is([1, 2])).toBe(false); 114 | expect(verificationObject.is(['invalid', 'disposable'])).toBe(false); 115 | }); 116 | }); 117 | 118 | describe('not', () => { 119 | it('should return false if the result matches the specified code', () => { 120 | const verificationObject = new VerificationObject(validResponse); 121 | expect(verificationObject.not(0)).toBe(false); 122 | expect(verificationObject.not('valid')).toBe(false); 123 | }); 124 | 125 | it('should return false if the result matches any of the specified codes', () => { 126 | const verificationObject = new VerificationObject(validResponse); 127 | expect(verificationObject.not([0, 1])).toBe(false); 128 | expect(verificationObject.not(['valid', 'invalid'])).toBe(false); 129 | }); 130 | 131 | it('should return true if the result does not match the specified code', () => { 132 | const verificationObject = new VerificationObject(validResponse); 133 | expect(verificationObject.not(1)).toBe(true); 134 | expect(verificationObject.not('invalid')).toBe(true); 135 | }); 136 | 137 | it('should return true if the result does not match any of the specified codes', () => { 138 | const verificationObject = new VerificationObject(validResponse); 139 | expect(verificationObject.not([1, 2])).toBe(true); 140 | expect(verificationObject.not(['invalid', 'disposable'])).toBe(true); 141 | }); 142 | }); 143 | 144 | describe('hasFlag', () => { 145 | it('should return true if the flag is present', () => { 146 | const verificationObject = new VerificationObject(validResponse); 147 | expect(verificationObject.hasFlag('has_dns')).toBe(true); 148 | expect(verificationObject.hasFlag('has_dns_mx')).toBe(true); 149 | expect(verificationObject.hasFlag('smtp_connectable')).toBe(true); 150 | }); 151 | 152 | it('should return false if the flag is not present', () => { 153 | const verificationObject = new VerificationObject(validResponse); 154 | expect(verificationObject.hasFlag('bad_syntax')).toBe(false); 155 | expect(verificationObject.hasFlag('disposable_email')).toBe(false); 156 | }); 157 | 158 | it('should return false if the response has no flags', () => { 159 | const noFlagsResponse = { ...validResponse, flags: [] }; 160 | const verificationObject = new VerificationObject(noFlagsResponse); 161 | expect(verificationObject.hasFlag('has_dns')).toBe(false); 162 | }); 163 | }); 164 | 165 | describe('static properties', () => { 166 | it('should expose result code constants', () => { 167 | expect(VerificationObject.valid).toBe(0); 168 | expect(VerificationObject.invalid).toBe(1); 169 | expect(VerificationObject.disposable).toBe(2); 170 | expect(VerificationObject.catchall).toBe(3); 171 | expect(VerificationObject.unknown).toBe(4); 172 | }); 173 | 174 | it('should expose helpers object with result codes and flags', () => { 175 | expect(VerificationObject.helpers.valid).toBe(0); 176 | expect(VerificationObject.helpers.invalid).toBe(1); 177 | expect(VerificationObject.helpers.disposable).toBe(2); 178 | expect(VerificationObject.helpers.catchall).toBe(3); 179 | expect(VerificationObject.helpers.unknown).toBe(4); 180 | 181 | expect(VerificationObject.helpers[0]).toBe('valid'); 182 | expect(VerificationObject.helpers[1]).toBe('invalid'); 183 | expect(VerificationObject.helpers[2]).toBe('disposable'); 184 | expect(VerificationObject.helpers[3]).toBe('catchall'); 185 | expect(VerificationObject.helpers[4]).toBe('unknown'); 186 | 187 | expect(VerificationObject.helpers.flags.has_dns).toBe('has_dns'); 188 | expect(VerificationObject.helpers.flags.has_dns_mx).toBe('has_dns_mx'); 189 | expect(VerificationObject.helpers.flags.bad_syntax).toBe('bad_syntax'); 190 | // Check a few more flags 191 | expect(VerificationObject.helpers.flags.free_email_host).toBe('free_email_host'); 192 | expect(VerificationObject.helpers.flags.disposable_email).toBe('disposable_email'); 193 | }); 194 | 195 | it('should expose deprecated numericCodes object', () => { 196 | expect(VerificationObject.numericCodes.valid).toBe(0); 197 | expect(VerificationObject.numericCodes.invalid).toBe(1); 198 | expect(VerificationObject.numericCodes.disposable).toBe(2); 199 | expect(VerificationObject.numericCodes.catchall).toBe(3); 200 | expect(VerificationObject.numericCodes.unknown).toBe(4); 201 | }); 202 | }); 203 | }); 204 | -------------------------------------------------------------------------------- /tests/setup.ts: -------------------------------------------------------------------------------- 1 | // This file is used to set up the Jest environment for TypeScript 2 | // It's referenced in the jest.config.js file 3 | 4 | // Make Jest functions globally available for ES modules 5 | import { jest } from '@jest/globals'; 6 | 7 | // Explicitly make jest available globally 8 | global.jest = jest; 9 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jest", "node"], 5 | "esModuleInterop": true, 6 | "noImplicitAny": false, 7 | "strictNullChecks": false, 8 | "strict": false, 9 | "noEmit": true, 10 | "allowJs": true, 11 | "checkJs": false 12 | }, 13 | "include": [ 14 | "**/*.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "./dist/cjs", 6 | "declaration": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "moduleResolution": "node", 6 | "declaration": true, 7 | "outDir": "./dist/esm", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "typeRoots": ["./node_modules/@types", "./src/types"] 13 | }, 14 | "include": ["src/**/*", "examples/**/*.ts"], 15 | "exclude": ["node_modules", "dist", "test"] 16 | } 17 | --------------------------------------------------------------------------------