fix: randomly choose a local address to query from

This commit is contained in:
orion 2024-02-07 15:50:11 -06:00
parent a91ed02d7d
commit e304960ee5
Signed by: orion
GPG Key ID: 6D4165AE4C928719
12 changed files with 6443 additions and 141 deletions

2
.gitignore vendored
View File

@ -11,3 +11,5 @@
.log
.purs-repl
.env
qed-mail/node_modules/
qed-mail/dist/

BIN
bun.lockb

Binary file not shown.

View File

@ -14,5 +14,5 @@
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": { "qed-mail": "^1.0.2" }
"dependencies": { "qed-mail": "./qed-mail/qed-mail-1.0.1@beta-1.tgz" }
}

130
qed-mail/.gitignore vendored
View File

@ -1,130 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View File

@ -1,6 +1,6 @@
{
"name": "qed-mail",
"version": "1.0.1",
"version": "1.0.1@beta-1",
"description": "📮 A NodeJS library for checking if an email address exists without sending any email.",
"main": "dist/index.js",
"types": "dist/index.d.ts",

Binary file not shown.

View File

@ -11,7 +11,7 @@ const DEFAULT_RESULT = {
smtp: { valid: false },
};
async function checkEmail(email: string): Promise<Result> {
async function checkEmail(email: string, {fromAddr}: {fromAddr?: string} = {}): Promise<Result> {
const result: Result = { email, ...DEFAULT_RESULT };
const syntax = checkSyntax(email);
@ -25,7 +25,7 @@ async function checkEmail(email: string): Promise<Result> {
}
const records = mx.mxRecords.sort((a, b) => a.priority - b.priority);
const smtp = await checkSMTP(email, records[0]!.exchange, 25);
const smtp = await checkSMTP(email, records[0]!.exchange, 25, {fromAddr});
if (!smtp.valid) {
return { ...result, syntax, mx, smtp };
}

View File

@ -1,16 +1,18 @@
import { Client } from "smtp-fetch";
import { SMTPResult } from "../types";
import { TcpNetConnectOpts } from "net";
async function checkSMTP(
to: string,
host: string,
port: number,
timeout: number = 5000
{fromAddr, timeout = 5000}: {timeout?: number, fromAddr?: string} = {},
): Promise<SMTPResult> {
const c = new Client(host, port);
try {
await c.connect({ timeout });
const options: Omit<TcpNetConnectOpts, 'host' | 'port'> = { timeout, localAddress: fromAddr };
await c.connect(options);
await c.mail("");
await c.rcpt(to);

6416
spago.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,14 @@ package:
- bifunctors
- effect
- either
- filterable
- foldable-traversable
- maybe
- newtype
- node-os
- nullable
- prelude
- random
- strings
- tailrec
- transformers

View File

@ -1,4 +1,4 @@
import { checkEmail } from 'qed-mail'
/** @type {(_: string) => () => Promise<import('qed-mail/src/types.js').Result>} */
export const checkEmailImpl = s => () => checkEmail(s)
/** @type {(email: string) => (addr: string | null) => () => Promise<import('qed-mail/src/types.js').Result>} */
export const checkEmailImpl = s => addr => () => checkEmail(s, {fromAddr: addr || undefined})

View File

@ -14,6 +14,8 @@ import Data.Either (Either(..))
import Data.Email (Email)
import Data.Email as Email
import Data.Eq.Generic (genericEq)
import Data.Filterable (filter)
import Data.Foldable (fold)
import Data.Generic.Rep (class Generic)
import Data.Int as Int
import Data.Maybe (maybe)
@ -24,9 +26,12 @@ import Data.Show.Generic (genericShow)
import Data.Traversable (for_)
import Effect (Effect)
import Effect.Aff (Aff, delay)
import Effect.Class (liftEffect)
import Effect.Random (randomInt)
import Node.OS as Node.OS
import Type.Function (type ($))
foreign import checkEmailImpl :: String -> Effect $ Promise $ QEDResult
foreign import checkEmailImpl :: String -> Nullable String -> Effect $ Promise $ QEDResult
data EmailError
= EmailUnreachable Email
@ -82,8 +87,12 @@ type QEDResult =
deliverableOnce :: Email -> ExceptT EmailError Aff Email
deliverableOnce email' = do
let email = Email.toString email'
{ reachable, syntax, mx, smtp } <- lift $ Promise.toAffE $ checkEmailImpl email
addrs <- liftEffect $ filter (not <<< _.internal) <$> fold <$> Node.OS.networkInterfaces
chosen <- liftEffect $ randomInt 0 (Array.length addrs - 1)
let
addr = _.address <$> Array.index addrs chosen
email = Email.toString email'
{ reachable, syntax, mx, smtp } <- lift $ Promise.toAffE $ checkEmailImpl email (Nullable.toNullable addr)
when (not syntax.valid) $ throwError $ EmailSyntaxInvalid email'
when (not mx.valid) $ throwError $ EmailMXInvalid email'
when (maybe true Array.null $ Nullable.toMaybe $ mx.mxRecords) $ throwError $ EmailMXNoRecords email'