Skip to content

Commit

Permalink
refactor(debounce): extract debounce connector to individual pkg
Browse files Browse the repository at this point in the history
  • Loading branch information
douglasduteil committed Jan 27, 2025
1 parent 05994e4 commit cddae15
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 46 deletions.
25 changes: 24 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
"main": "src/index.js",
"workspaces": [
"packages/core",
"packages/crisp",
"packages/debounce",
"packages/email",
"packages/insee",
"packages/crisp",
"packages/identite"
],
"scripts": {
Expand Down Expand Up @@ -53,6 +54,7 @@
},
"dependencies": {
"@gouvfr-lasuite/proconnect.core": "workspace:*",
"@gouvfr-lasuite/proconnect.debounce": "workspace:*",
"@gouvfr-lasuite/proconnect.crisp": "workspace:*",
"@gouvfr-lasuite/proconnect.email": "workspace:*",
"@gouvfr-lasuite/proconnect.identite": "workspace:*",
Expand Down
58 changes: 58 additions & 0 deletions packages/debounce/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@gouvfr-lasuite/proconnect.debounce",
"version": "0.3.2",
"homepage": "https://github.com/numerique-gouv/moncomptepro/tree/master/packages/debounce#readme",
"bugs": "https://github.com/numerique-gouv/moncomptepro/issues",
"repository": {
"type": "git",
"url": "git+https://github.com/numerique-gouv/moncomptepro.git",
"directory": "packages/debounce"
},
"license": "MIT",
"sideEffects": false,
"type": "module",
"imports": {},
"exports": {
"./api": {
"require": "./dist/api/index.cjs",
"import": "./dist/api/index.js",
"types": "./dist/api/index.d.ts"
}
},
"typesVersions": {
"*": {
"api": [
"./dist/api/index.d.ts"
]
}
},
"scripts": {
"build": "pkgroll --tsconfig=tsconfig.lib.json",
"check": "npm run build -- --noEmit",
"dev": "npm run build -- --watch --preserveWatchOutput",
"test": "mocha"
},
"mocha": {
"reporter": "spec",
"require": [
"tsx"
],
"spec": "src/**/*.test.ts"
},
"dependencies": {
"axios": "^1.7.7"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.0",
"@types/mocha": "^10.0.10",
"@types/node": "^22.10.2",
"chai": "^5.1.2",
"mocha": "^11.0.1",
"pkgroll": "^2.6.1",
"tsx": "^4.19.2"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
3 changes: 3 additions & 0 deletions packages/debounce/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//

export * from "./single-validation.js";
Empty file.
34 changes: 34 additions & 0 deletions packages/debounce/src/api/single-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//

import type { DebounceSuccessResponse } from "#src/types/index.js";
import axios, { type AxiosRequestConfig, type AxiosResponse } from "axios";

//

/**
* Perform a single email validation request.
*
* @see https://developers.debounce.io/reference/single-validation#response-parameters
* @param apiKey the debounce.io API key
* @param config the Axios request config
* @returns Debounce Single Validation response
*/
export function singleValidationFactory(
apiKey: string,
config?: AxiosRequestConfig,
) {
return async function singleValidation(email: string) {
const {
data: { debounce },
}: AxiosResponse<DebounceSuccessResponse> = await axios({
method: "get",
url: `https://api.debounce.io/v1/?email=${email}&api=${apiKey}`,
headers: {
accept: "application/json",
},
...config,
});

return debounce;
};
}
47 changes: 47 additions & 0 deletions packages/debounce/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//

/**
* @see https://developers.debounce.io/reference/responses#error-response
*/
export interface DebounceErrorResponse {
debounce: {
error: string;
code: "0";
};
success: "0";
}

/**
* @see https://developers.debounce.io/reference/responses#success-response
*/
export interface DebounceSuccessResponse {
debounce: {
// The email address you are requesting to validate.
// ex: '[email protected]'
email: string;
// DeBounce validation response code.
// ex: '6'
code: string;
// Is the email role-based or not. Role emails such as "sales@", "webmaster@" etc. are not suitable for sending marketing emails to.
// ['true', 'false']
role: string;
// Is the email from a free email provider - like Gmail - or not.
// ['true', 'false']
free_email: string;
// The final result of the validation process. This response will help to determine whether you should send marketing emails to a recipient or not.
// ['Invalid', 'Risky', 'Safe to Send', 'Unknown']
result: string;
// The reason why the result is given.
// ex: 'Bounce, Role'
reason: string;
// Is it suggested that you send transactional emails to the recipient or not (0: no, 1: yes). Generally, it is suggested to send transactional emails to Valid, Accept-all, and Unknown emails.
// ['0', '1']
send_transactional: string;
// If you use a misspelled email address like someemail@gmial.com, the validation engine tries to suggest you the corrected email address.
// ex: '[email protected]', ''
did_you_mean: string;
};

success: "1";
balance: `${number}`;
}
18 changes: 18 additions & 0 deletions packages/debounce/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
"rootDir": "src",
"types": ["node"],
"module": "NodeNext",
"moduleResolution": "nodenext",
"verbatimModuleSyntax": true,
"paths": {
"#src/*": ["./src/*"]
}
},
"extends": "@tsconfig/node22/tsconfig.json",
"references": []
}
9 changes: 9 additions & 0 deletions packages/debounce/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"exclude": ["src/**/*.test.ts"],
"extends": "./tsconfig.json",
"include": ["src"]
}
2 changes: 1 addition & 1 deletion src/config/env.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const connectorEnvSchema = z.object({
CRISP_WEBSITE_ID: z.string().default(""),
CRISP_MODERATION_TAG: zCoerceArray(z.string()).default("identite,moderation"),
DATABASE_URL: z.string().url(),
DEBOUNCE_API_KEY: z.string().optional(),
DEBOUNCE_API_KEY: z.string().default(""),
INSEE_CONSUMER_KEY: z.string(),
INSEE_CONSUMER_SECRET: z.string(),
REDIS_URL: z.string().url().default("redis://:@127.0.0.1:6379"),
Expand Down
50 changes: 7 additions & 43 deletions src/connectors/debounce.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import axios, { type AxiosResponse } from "axios";
import { singleValidationFactory } from "@gouvfr-lasuite/proconnect.debounce/api";
import {
DEBOUNCE_API_KEY,
EMAIL_DELIVERABILITY_WHITELIST,
Expand All @@ -8,41 +8,15 @@ import {
import { getEmailDomain } from "../services/email";
import { logger } from "../services/log";

// documentation: https://developers.debounce.io/reference/single-validation#response-parameters
type DebounceResponse = {
debounce: {
// The email address you are requesting to validate.
// ex: '[email protected]'
email: string;
// DeBounce validation response code.
// ex: '6'
code: string;
// Is the email role-based or not. Role emails such as "sales@", "webmaster@" etc. are not suitable for sending marketing emails to.
// ['true', 'false']
role: string;
// Is the email from a free email provider - like Gmail - or not.
// ['true', 'false']
free_email: string;
// The final result of the validation process. This response will help to determine whether you should send marketing emails to a recipient or not.
// ['Invalid', 'Risky', 'Safe to Send', 'Unknown']
result: string;
// The reason why the result is given.
// ex: 'Bounce, Role'
reason: string;
// Is it suggested that you send transactional emails to the recipient or not (0: no, 1: yes). Generally, it is suggested to send transactional emails to Valid, Accept-all, and Unknown emails.
// ['0', '1']
send_transactional: string;
// If you use a misspelled email address like someemail@gmial.com, the validation engine tries to suggest you the corrected email address.
// ex: '[email protected]', ''
did_you_mean: string;
};
};

type EmailDebounceInfo = {
isEmailSafeToSend: boolean;
didYouMean?: string;
};

const singleValidation = singleValidationFactory(DEBOUNCE_API_KEY, {
timeout: HTTP_CLIENT_TIMEOUT,
});

export const isEmailSafeToSendTransactional = async (
email: string,
): Promise<EmailDebounceInfo> => {
Expand All @@ -60,18 +34,8 @@ export const isEmailSafeToSendTransactional = async (
}

try {
const {
data: {
debounce: { send_transactional, did_you_mean: didYouMean },
},
}: AxiosResponse<DebounceResponse> = await axios({
method: "get",
url: `https://api.debounce.io/v1/?email=${email}&api=${DEBOUNCE_API_KEY}`,
headers: {
accept: "application/json",
},
timeout: HTTP_CLIENT_TIMEOUT,
});
const { send_transactional, did_you_mean: didYouMean } =
await singleValidation(email);

logger.info(
`Email address "${email}" is ${
Expand Down

0 comments on commit cddae15

Please sign in to comment.