dymo-api 1.2.37 → 1.2.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/dymo-api.cjs +961 -816
- package/dist/esm/dymo-api.js +961 -816
- package/dist/types/branches/private/functions/extractWithTextly/index.d.ts +8 -11
- package/dist/types/branches/private/functions/extractWithTextly/index.d.ts.map +1 -1
- package/dist/types/branches/private/functions/getRandom/index.d.ts +8 -11
- package/dist/types/branches/private/functions/getRandom/index.d.ts.map +1 -1
- package/dist/types/branches/private/functions/isValidDataRaw/index.d.ts +8 -10
- package/dist/types/branches/private/functions/isValidDataRaw/index.d.ts.map +1 -1
- package/dist/types/branches/private/functions/isValidEmail/index.d.ts +9 -15
- package/dist/types/branches/private/functions/isValidEmail/index.d.ts.map +1 -1
- package/dist/types/branches/private/functions/isValidIP/index.d.ts +10 -15
- package/dist/types/branches/private/functions/isValidIP/index.d.ts.map +1 -1
- package/dist/types/branches/private/functions/isValidPhone/index.d.ts +10 -15
- package/dist/types/branches/private/functions/isValidPhone/index.d.ts.map +1 -1
- package/dist/types/branches/private/functions/protectReq/index.d.ts +12 -1
- package/dist/types/branches/private/functions/protectReq/index.d.ts.map +1 -1
- package/dist/types/branches/private/functions/sendEmail/index.d.ts +10 -13
- package/dist/types/branches/private/functions/sendEmail/index.d.ts.map +1 -1
- package/dist/types/branches/public/functions/getPrayerTimes/index.d.ts +8 -12
- package/dist/types/branches/public/functions/getPrayerTimes/index.d.ts.map +1 -1
- package/dist/types/branches/public/functions/isValidPwd/index.d.ts +9 -24
- package/dist/types/branches/public/functions/isValidPwd/index.d.ts.map +1 -1
- package/dist/types/branches/public/functions/satinize/index.d.ts +9 -8
- package/dist/types/branches/public/functions/satinize/index.d.ts.map +1 -1
- package/dist/types/dymo-api.d.ts +22 -24
- package/dist/types/dymo-api.d.ts.map +1 -1
- package/dist/types/lib/resilience/fallback.d.ts.map +1 -1
- package/dist/types/lib/types/data-verifier.d.ts +27 -1
- package/dist/types/lib/types/data-verifier.d.ts.map +1 -1
- package/package.json +90 -88
package/dist/cjs/dymo-api.cjs
CHANGED
|
@@ -16,807 +16,1015 @@ const validBaseURL = (baseUrl) => {
|
|
|
16
16
|
if (/^(https:\/\/api\.tpeoficial\.com$|http:\/\/(localhost:\d+|dymoapi:\d+))$/.test(baseUrl)) return baseUrl;
|
|
17
17
|
else throw new Error("[Dymo API] Invalid URL. It must be https://api.tpeoficial.com or start with http://localhost or http://dymoapi followed by a port.");
|
|
18
18
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
class FallbackDataGenerator {
|
|
20
|
+
static generateFallbackData(method, inputData) {
|
|
21
|
+
switch (method) {
|
|
22
|
+
case "isValidData":
|
|
23
|
+
case "isValidDataRaw":
|
|
24
|
+
return this.generateDataValidationAnalysis(inputData);
|
|
25
|
+
case "isValidEmail":
|
|
26
|
+
return this.generateEmailValidatorResponse(inputData);
|
|
27
|
+
case "isValidIP":
|
|
28
|
+
return this.generateIPValidatorResponse(inputData);
|
|
29
|
+
case "isValidPhone":
|
|
30
|
+
return this.generatePhoneValidatorResponse(inputData);
|
|
31
|
+
case "protectReq":
|
|
32
|
+
return this.generateHTTPRequest(inputData);
|
|
33
|
+
case "sendEmail":
|
|
34
|
+
return this.generateEmailStatus();
|
|
35
|
+
case "getRandom":
|
|
36
|
+
return this.generateSRNSummary(inputData);
|
|
37
|
+
case "extractWithTextly":
|
|
38
|
+
return this.generateExtractWithTextly(inputData);
|
|
39
|
+
case "getPrayerTimes":
|
|
40
|
+
return this.generatePrayerTimes(inputData);
|
|
41
|
+
case "satinize":
|
|
42
|
+
case "satinizer":
|
|
43
|
+
return this.generateSatinizedInputAnalysis(inputData);
|
|
44
|
+
case "isValidPwd":
|
|
45
|
+
return this.generatePasswordValidationResult(inputData);
|
|
46
|
+
default:
|
|
47
|
+
throw new Error(`Unknown method for fallback: ${method}`);
|
|
48
|
+
}
|
|
27
49
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const params = { password: encodeURIComponent(password) };
|
|
33
|
-
if (email) {
|
|
34
|
-
if (!/^[a-zA-Z0-9._\-+]+@?[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(email)) throw customError(1500, "If you provide an email address it must be valid.");
|
|
35
|
-
params.email = encodeURIComponent(email);
|
|
50
|
+
static validateURL(url) {
|
|
51
|
+
if (!url) return false;
|
|
52
|
+
const urlRegex = /^https?:\/\/(?:[-\w.])+(?:\:[0-9]+)?(?:\/(?:[\w\/_.])*(?:\?(?:[\w&=%.])*)?(?:\#(?:[\w.])*)?)?$/;
|
|
53
|
+
return urlRegex.test(url);
|
|
36
54
|
}
|
|
37
|
-
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
params.bannedWords = bannedWords;
|
|
55
|
+
static validateEmail(email) {
|
|
56
|
+
if (!email) return false;
|
|
57
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
58
|
+
return emailRegex.test(email);
|
|
42
59
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return response.data;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
throw customError(5e3, error.response?.data?.message || error.message);
|
|
60
|
+
static validateDomain(domain) {
|
|
61
|
+
if (!domain) return false;
|
|
62
|
+
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])*$/;
|
|
63
|
+
if (!domainRegex.test(domain)) return false;
|
|
64
|
+
const parts = domain.split(".");
|
|
65
|
+
return parts.length >= 2 && parts[parts.length - 1].length > 0;
|
|
52
66
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
static validateCreditCard(creditCard) {
|
|
68
|
+
if (!creditCard) return false;
|
|
69
|
+
const cardNumber = typeof creditCard === "string" ? creditCard : creditCard?.pan || "";
|
|
70
|
+
if (!cardNumber) return false;
|
|
71
|
+
const cardRegex = /^\d{13,19}$/;
|
|
72
|
+
if (!cardRegex.test(cardNumber.replace(/\s/g, ""))) return false;
|
|
73
|
+
const digits = cardNumber.replace(/\s/g, "").split("").map(Number);
|
|
74
|
+
let sum = 0;
|
|
75
|
+
let isEven = false;
|
|
76
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
77
|
+
let digit = digits[i];
|
|
78
|
+
if (isEven) {
|
|
79
|
+
digit *= 2;
|
|
80
|
+
if (digit > 9) digit -= 9;
|
|
81
|
+
}
|
|
82
|
+
sum += digit;
|
|
83
|
+
isEven = !isEven;
|
|
84
|
+
}
|
|
85
|
+
return sum % 10 === 0;
|
|
61
86
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const response = await axiosClient.post("/private/textly/extract", data, { headers: { "Content-Type": "application/json" } });
|
|
69
|
-
return response.data;
|
|
70
|
-
} catch (error) {
|
|
71
|
-
throw customError(5e3, error.response?.data?.message || error.message);
|
|
87
|
+
static validateIP(ip) {
|
|
88
|
+
if (!ip) return false;
|
|
89
|
+
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
90
|
+
const ipv6Regex = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
|
91
|
+
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
|
72
92
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (data.min >= data.max) throw customError(1500, "'min' must be less than 'max'.");
|
|
78
|
-
if (data.min < -1e9 || data.min > 1e9) throw customError(1500, "'min' must be an integer in the interval [-1000000000}, 1000000000].");
|
|
79
|
-
if (data.max < -1e9 || data.max > 1e9) throw customError(1500, "'max' must be an integer in the interval [-1000000000}, 1000000000].");
|
|
80
|
-
try {
|
|
81
|
-
const response = await axiosClient.post("/private/srng", data, { headers: { "Content-Type": "application/json" } });
|
|
82
|
-
return response.data;
|
|
83
|
-
} catch (error) {
|
|
84
|
-
throw customError(5e3, error.response?.data?.message || error.message);
|
|
93
|
+
static validatePhone(phone) {
|
|
94
|
+
if (!phone) return false;
|
|
95
|
+
const phoneRegex = /^\+?[1-9]\d{1,14}$/;
|
|
96
|
+
return phoneRegex.test(phone.replace(/[^\d+]/g, ""));
|
|
85
97
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.warn("[Dymo API] DRY_RUN mode is enabled. No requests with real data will be processed until you switch to LIVE mode.");
|
|
92
|
-
return {
|
|
93
|
-
ip,
|
|
94
|
-
allow: true,
|
|
95
|
-
reasons: [],
|
|
96
|
-
response: "CHANGE TO LIVE MODE"
|
|
97
|
-
};
|
|
98
|
+
static validateWallet(wallet) {
|
|
99
|
+
if (!wallet) return false;
|
|
100
|
+
const bitcoinRegex = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/;
|
|
101
|
+
const ethereumRegex = /^0x[a-fA-F0-9]{40}$/;
|
|
102
|
+
return bitcoinRegex.test(wallet) || ethereumRegex.test(wallet);
|
|
98
103
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return
|
|
110
|
-
ip: responseIP.ip,
|
|
111
|
-
allow: false,
|
|
112
|
-
reasons: ["INVALID"],
|
|
113
|
-
response: responseIP
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if (rules.deny.includes("FRAUD") && responseIP.fraud) reasons.push("FRAUD");
|
|
117
|
-
if (rules.deny.includes("TOR_NETWORK") && responseIP.plugins.torNetwork) reasons.push("TOR_NETWORK");
|
|
118
|
-
if (rules.deny.includes("HIGH_RISK_SCORE") && responseIP.plugins.riskScore >= 80) reasons.push("HIGH_RISK_SCORE");
|
|
119
|
-
for (const rule of rules.deny) {
|
|
120
|
-
if (rule.startsWith("COUNTRY:")) {
|
|
121
|
-
const block = rule.split(":")[1];
|
|
122
|
-
if (responseIP.countryCode === block) reasons.push(`COUNTRY:${block}`);
|
|
123
|
-
}
|
|
104
|
+
static validateIBAN(iban) {
|
|
105
|
+
if (!iban) return false;
|
|
106
|
+
const ibanRegex = /^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/;
|
|
107
|
+
return ibanRegex.test(iban.replace(/\s/g, "").toUpperCase());
|
|
108
|
+
}
|
|
109
|
+
static extractDomain(url) {
|
|
110
|
+
if (!url) return "";
|
|
111
|
+
try {
|
|
112
|
+
return new URL(url).hostname;
|
|
113
|
+
} catch {
|
|
114
|
+
return "";
|
|
124
115
|
}
|
|
125
|
-
return {
|
|
126
|
-
ip: responseIP.ip,
|
|
127
|
-
allow: reasons.length === 0,
|
|
128
|
-
reasons,
|
|
129
|
-
response: responseIP
|
|
130
|
-
};
|
|
131
|
-
} catch (error) {
|
|
132
|
-
const statusCode = error.response?.status || 500;
|
|
133
|
-
const errorMessage = error.response?.data?.message || error.message;
|
|
134
|
-
const errorDetails = JSON.stringify(error.response?.data || {});
|
|
135
|
-
throw customError(5e3, `Error ${statusCode}: ${errorMessage}. Details: ${errorDetails}`);
|
|
136
116
|
}
|
|
137
|
-
|
|
138
|
-
const isValidPhone = async (axiosClient, phone, rules) => {
|
|
139
|
-
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
140
|
-
if (rules.deny.length === 0) throw customError(1500, "You must provide at least one deny rule.");
|
|
141
|
-
if (rules.mode === "DRY_RUN") {
|
|
142
|
-
console.warn("[Dymo API] DRY_RUN mode is enabled. No requests with real data will be processed until you switch to LIVE mode.");
|
|
117
|
+
static generateDataValidationAnalysis(inputData) {
|
|
143
118
|
return {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
119
|
+
url: {
|
|
120
|
+
valid: this.validateURL(inputData?.url),
|
|
121
|
+
fraud: false,
|
|
122
|
+
freeSubdomain: false,
|
|
123
|
+
customTLD: false,
|
|
124
|
+
url: inputData?.url || "",
|
|
125
|
+
domain: this.extractDomain(inputData?.url),
|
|
126
|
+
plugins: {
|
|
127
|
+
blocklist: false,
|
|
128
|
+
compromiseDetector: false,
|
|
129
|
+
mxRecords: [],
|
|
130
|
+
nsfw: false,
|
|
131
|
+
reputation: "unknown",
|
|
132
|
+
riskScore: 0,
|
|
133
|
+
torNetwork: false,
|
|
134
|
+
typosquatting: 0,
|
|
135
|
+
urlShortener: false
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
email: this.generateEmailDataAnalysis(inputData?.email),
|
|
139
|
+
phone: this.generatePhoneDataAnalysis(inputData?.phone),
|
|
140
|
+
domain: {
|
|
141
|
+
valid: this.validateDomain(inputData?.domain),
|
|
142
|
+
fraud: false,
|
|
143
|
+
freeSubdomain: false,
|
|
144
|
+
customTLD: false,
|
|
145
|
+
domain: inputData?.domain || "",
|
|
146
|
+
plugins: {
|
|
147
|
+
blocklist: false,
|
|
148
|
+
compromiseDetector: false,
|
|
149
|
+
mxRecords: [],
|
|
150
|
+
nsfw: false,
|
|
151
|
+
reputation: "unknown",
|
|
152
|
+
riskScore: 0,
|
|
153
|
+
torNetwork: false,
|
|
154
|
+
typosquatting: 0,
|
|
155
|
+
urlShortener: false
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
creditCard: {
|
|
159
|
+
valid: this.validateCreditCard(inputData?.creditCard),
|
|
160
|
+
fraud: false,
|
|
161
|
+
test: false,
|
|
162
|
+
type: "unknown",
|
|
163
|
+
creditCard: typeof inputData?.creditCard === "string" ? inputData.creditCard : inputData?.creditCard?.pan || "",
|
|
164
|
+
plugins: {
|
|
165
|
+
blocklist: false,
|
|
166
|
+
riskScore: 0
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
ip: this.generateIPDataAnalysis(inputData?.ip),
|
|
170
|
+
wallet: {
|
|
171
|
+
valid: this.validateWallet(inputData?.wallet),
|
|
172
|
+
fraud: false,
|
|
173
|
+
wallet: inputData?.wallet || "",
|
|
174
|
+
type: "unknown",
|
|
175
|
+
plugins: {
|
|
176
|
+
blocklist: false,
|
|
177
|
+
riskScore: 0,
|
|
178
|
+
torNetwork: false
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
userAgent: {
|
|
182
|
+
valid: false,
|
|
183
|
+
fraud: false,
|
|
184
|
+
userAgent: inputData?.userAgent || "",
|
|
185
|
+
bot: true,
|
|
186
|
+
device: { type: "unknown", brand: "unknown" },
|
|
187
|
+
plugins: {
|
|
188
|
+
blocklist: false,
|
|
189
|
+
riskScore: 0
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
iban: {
|
|
193
|
+
valid: this.validateIBAN(inputData?.iban),
|
|
194
|
+
fraud: false,
|
|
195
|
+
iban: inputData?.iban || "",
|
|
196
|
+
plugins: {
|
|
197
|
+
blocklist: false,
|
|
198
|
+
riskScore: 0
|
|
199
|
+
}
|
|
200
|
+
}
|
|
148
201
|
};
|
|
149
202
|
}
|
|
150
|
-
|
|
151
|
-
const responsePhone = (await axiosClient.post("/private/secure/verify", {
|
|
152
|
-
phone,
|
|
153
|
-
plugins: [
|
|
154
|
-
rules.deny.includes("HIGH_RISK_SCORE") ? "riskScore" : void 0
|
|
155
|
-
].filter(Boolean)
|
|
156
|
-
}, { headers: { "Content-Type": "application/json" } })).data.phone;
|
|
157
|
-
let reasons = [];
|
|
158
|
-
if (rules.deny.includes("INVALID") && !responsePhone.valid) {
|
|
159
|
-
return {
|
|
160
|
-
phone: responsePhone.phone,
|
|
161
|
-
allow: false,
|
|
162
|
-
reasons: ["INVALID"],
|
|
163
|
-
response: responsePhone
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
if (rules.deny.includes("FRAUD") && responsePhone.fraud) reasons.push("FRAUD");
|
|
167
|
-
if (rules.deny.includes("HIGH_RISK_SCORE") && responsePhone.plugins.riskScore >= 80) reasons.push("HIGH_RISK_SCORE");
|
|
168
|
-
for (const rule of rules.deny) {
|
|
169
|
-
if (rule.startsWith("COUNTRY:")) {
|
|
170
|
-
const block = rule.split(":")[1];
|
|
171
|
-
if (responsePhone.countryCode === block) reasons.push(`COUNTRY:${block}`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
203
|
+
static generateEmailValidatorResponse(inputData) {
|
|
174
204
|
return {
|
|
175
|
-
|
|
176
|
-
allow:
|
|
177
|
-
reasons,
|
|
178
|
-
response:
|
|
205
|
+
email: inputData?.email || "",
|
|
206
|
+
allow: this.validateEmail(inputData?.email),
|
|
207
|
+
reasons: this.validateEmail(inputData?.email) ? [] : ["INVALID"],
|
|
208
|
+
response: this.generateEmailDataAnalysis(inputData?.email)
|
|
179
209
|
};
|
|
180
|
-
} catch (error) {
|
|
181
|
-
const statusCode = error.response?.status || 500;
|
|
182
|
-
const errorMessage = error.response?.data?.message || error.message;
|
|
183
|
-
const errorDetails = JSON.stringify(error.response?.data || {});
|
|
184
|
-
throw customError(5e3, `Error ${statusCode}: ${errorMessage}. Details: ${errorDetails}`);
|
|
185
210
|
}
|
|
186
|
-
|
|
187
|
-
const getUserAgent = (req) => {
|
|
188
|
-
return req.headers?.["user-agent"] || req.headers?.["User-Agent"];
|
|
189
|
-
};
|
|
190
|
-
const getIp = (req) => {
|
|
191
|
-
return req.ip || req.headers?.["x-forwarded-for"] || req.connection?.remoteAddress || req.socket?.remoteAddress || req.req?.socket?.remoteAddress;
|
|
192
|
-
};
|
|
193
|
-
const handleRequest = (req) => {
|
|
194
|
-
return {
|
|
195
|
-
body: req.body,
|
|
196
|
-
userAgent: getUserAgent(req),
|
|
197
|
-
ip: getIp(req)
|
|
198
|
-
};
|
|
199
|
-
};
|
|
200
|
-
const protectReq = async (axiosClient, req, rules) => {
|
|
201
|
-
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
202
|
-
const reqData = handleRequest(req);
|
|
203
|
-
if (!reqData.userAgent || !reqData.ip) throw customError(1500, "You must provide user agent and ip.");
|
|
204
|
-
if (rules.mode === "DRY_RUN") {
|
|
205
|
-
console.warn("[Dymo API] DRY_RUN mode is enabled. No requests with real data will be processed until you switch to LIVE mode.");
|
|
211
|
+
static generateEmailDataAnalysis(email) {
|
|
206
212
|
return {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
213
|
+
valid: this.validateEmail(email),
|
|
214
|
+
fraud: false,
|
|
215
|
+
proxiedEmail: false,
|
|
216
|
+
freeSubdomain: false,
|
|
217
|
+
corporate: false,
|
|
218
|
+
email: email || "",
|
|
219
|
+
realUser: "",
|
|
220
|
+
didYouMean: null,
|
|
221
|
+
noReply: false,
|
|
222
|
+
customTLD: false,
|
|
223
|
+
domain: "",
|
|
224
|
+
roleAccount: false,
|
|
225
|
+
plugins: {
|
|
226
|
+
mxRecords: [],
|
|
227
|
+
blocklist: false,
|
|
228
|
+
compromiseDetector: false,
|
|
229
|
+
nsfw: false,
|
|
230
|
+
reputation: "unknown",
|
|
231
|
+
riskScore: 0,
|
|
232
|
+
socialFootprint: {
|
|
233
|
+
totalChecked: 0,
|
|
234
|
+
platforms: []
|
|
235
|
+
},
|
|
236
|
+
torNetwork: false,
|
|
237
|
+
typosquatting: 0,
|
|
238
|
+
urlShortener: false
|
|
239
|
+
}
|
|
211
240
|
};
|
|
212
241
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
ip:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}, { headers: { "Content-Type": "application/json" } });
|
|
220
|
-
return response.data;
|
|
221
|
-
} catch (error) {
|
|
222
|
-
const statusCode = error.response?.status || 500;
|
|
223
|
-
const errorMessage = error.response?.data?.message || error.message;
|
|
224
|
-
const errorDetails = JSON.stringify(error.response?.data || {});
|
|
225
|
-
throw customError(5e3, `Error ${statusCode}: ${errorMessage}. Details: ${errorDetails}`);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
const fs = {};
|
|
229
|
-
const convertTailwindToInlineCss = (htmlContent) => {
|
|
230
|
-
return htmlContent.replace(/class="([^"]+)"( style="([^"]+)")?/g, (match, classList, _, existingStyle) => {
|
|
231
|
-
const compiledStyles = twToCss.twi(classList, { minify: true, merge: true });
|
|
232
|
-
return match.replace(/class="[^"]+"/, "").replace(/ style="[^"]+"/, "").concat(` style="${existingStyle ? `${existingStyle.trim().slice(0, -1)}; ${compiledStyles}` : compiledStyles}"`);
|
|
233
|
-
});
|
|
234
|
-
};
|
|
235
|
-
const sendEmail = async (axiosClient, data) => {
|
|
236
|
-
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
237
|
-
if (!data.from) throw customError(1500, "You must provide an email address from which the following will be sent.");
|
|
238
|
-
if (!data.to) throw customError(1500, "You must provide an email to be sent to.");
|
|
239
|
-
if (!data.subject) throw customError(1500, "You must provide a subject for the email to be sent.");
|
|
240
|
-
if (!data.html && !data.react && !React.isValidElement(data.react)) throw customError(1500, "You must provide HTML or a React component.");
|
|
241
|
-
if (data.html && data.react) throw customError(1500, "You must provide only HTML or a React component, not both.");
|
|
242
|
-
try {
|
|
243
|
-
if (data.react) {
|
|
244
|
-
data.html = await render.render(data.react);
|
|
245
|
-
delete data.react;
|
|
246
|
-
}
|
|
247
|
-
if (data.options && data.options.composeTailwindClasses) {
|
|
248
|
-
data.html = convertTailwindToInlineCss(data.html);
|
|
249
|
-
delete data.options.composeTailwindClasses;
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
throw customError(1500, `An error occurred while rendering your React component. Details: ${error}`);
|
|
253
|
-
}
|
|
254
|
-
try {
|
|
255
|
-
let totalSize = 0;
|
|
256
|
-
if (data.attachments && Array.isArray(data.attachments)) {
|
|
257
|
-
const processedAttachments = await Promise.all(
|
|
258
|
-
data.attachments.map(async (attachment) => {
|
|
259
|
-
if (attachment.path && attachment.content || !attachment.path && !attachment.content) throw customError(1500, "You must provide either 'path' or 'content', not both.");
|
|
260
|
-
let contentBuffer;
|
|
261
|
-
if (attachment.path) contentBuffer = await fs.readFile(path.resolve(attachment.path));
|
|
262
|
-
else if (attachment.content) contentBuffer = attachment.content instanceof Buffer ? attachment.content : Buffer.from(attachment.content);
|
|
263
|
-
totalSize += Buffer.byteLength(contentBuffer);
|
|
264
|
-
if (totalSize > 40 * 1024 * 1024) throw customError(1500, "Attachments exceed the maximum allowed size of 40 MB.");
|
|
265
|
-
return {
|
|
266
|
-
filename: attachment.filename || path.basename(attachment.path || ""),
|
|
267
|
-
content: contentBuffer,
|
|
268
|
-
cid: attachment.cid || attachment.filename
|
|
269
|
-
};
|
|
270
|
-
})
|
|
271
|
-
);
|
|
272
|
-
data.attachments = processedAttachments;
|
|
273
|
-
}
|
|
274
|
-
const response = await axiosClient.post("/private/sender/sendEmail", data);
|
|
275
|
-
return response.data;
|
|
276
|
-
} catch (error) {
|
|
277
|
-
throw customError(5e3, error.response?.data?.message || error.message);
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
class RateLimitManager {
|
|
281
|
-
constructor() {
|
|
282
|
-
this.tracker = {};
|
|
283
|
-
}
|
|
284
|
-
static getInstance() {
|
|
285
|
-
if (!RateLimitManager.instance) RateLimitManager.instance = new RateLimitManager();
|
|
286
|
-
return RateLimitManager.instance;
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Parses a header value that could be a number or "unlimited".
|
|
290
|
-
* Returns undefined if the value is "unlimited", null, undefined, or invalid.
|
|
291
|
-
*/
|
|
292
|
-
parseHeaderValue(value) {
|
|
293
|
-
if (value === null || value === void 0) return void 0;
|
|
294
|
-
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
295
|
-
const strValue = String(value).trim().toLowerCase();
|
|
296
|
-
if (!strValue || strValue === "unlimited") return void 0;
|
|
297
|
-
const parsed = parseInt(strValue, 10);
|
|
298
|
-
return isNaN(parsed) || parsed < 0 ? void 0 : parsed;
|
|
299
|
-
}
|
|
300
|
-
updateRateLimit(clientId, headers) {
|
|
301
|
-
if (!this.tracker[clientId]) this.tracker[clientId] = {};
|
|
302
|
-
const limitInfo = this.tracker[clientId];
|
|
303
|
-
const limitRequests = headers["x-ratelimit-limit-requests"];
|
|
304
|
-
const remainingRequests = headers["x-ratelimit-remaining-requests"];
|
|
305
|
-
const resetRequests = headers["x-ratelimit-reset-requests"];
|
|
306
|
-
const retryAfter = headers["retry-after"];
|
|
307
|
-
const parsedLimit = this.parseHeaderValue(limitRequests);
|
|
308
|
-
const parsedRemaining = this.parseHeaderValue(remainingRequests);
|
|
309
|
-
const parsedRetryAfter = this.parseHeaderValue(retryAfter);
|
|
310
|
-
if (parsedLimit !== void 0) limitInfo.limit = parsedLimit;
|
|
311
|
-
if (parsedRemaining !== void 0) limitInfo.remaining = parsedRemaining;
|
|
312
|
-
if (typeof remainingRequests === "string" && remainingRequests.trim().toLowerCase() === "unlimited") limitInfo.isUnlimited = true;
|
|
313
|
-
if (resetRequests && typeof resetRequests === "string") limitInfo.resetTime = resetRequests;
|
|
314
|
-
if (parsedRetryAfter !== void 0) limitInfo.retryAfter = parsedRetryAfter;
|
|
315
|
-
limitInfo.lastUpdated = Date.now();
|
|
316
|
-
}
|
|
317
|
-
isRateLimited(clientId) {
|
|
318
|
-
const limitInfo = this.tracker[clientId];
|
|
319
|
-
if (!limitInfo) return false;
|
|
320
|
-
if (limitInfo.isUnlimited) return false;
|
|
321
|
-
return limitInfo.remaining !== void 0 && limitInfo.remaining <= 0;
|
|
322
|
-
}
|
|
323
|
-
getRetryAfter(clientId) {
|
|
324
|
-
return this.tracker[clientId]?.retryAfter;
|
|
325
|
-
}
|
|
326
|
-
clearExpiredLimits() {
|
|
327
|
-
const now = Date.now();
|
|
328
|
-
Object.keys(this.tracker).forEach((clientId) => {
|
|
329
|
-
const limitInfo = this.tracker[clientId];
|
|
330
|
-
if (limitInfo.lastUpdated && now - limitInfo.lastUpdated > 3e5) delete this.tracker[clientId];
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
class ResilienceManager {
|
|
335
|
-
constructor(config2 = {}, clientId = "default") {
|
|
336
|
-
this.config = {
|
|
337
|
-
fallbackEnabled: config2.fallbackEnabled ?? false,
|
|
338
|
-
retryAttempts: Math.max(0, config2.retryAttempts ?? 2),
|
|
339
|
-
// Number of additional retries
|
|
340
|
-
retryDelay: Math.max(0, config2.retryDelay ?? 1e3)
|
|
242
|
+
static generateIPValidatorResponse(inputData) {
|
|
243
|
+
return {
|
|
244
|
+
ip: inputData?.ip || "",
|
|
245
|
+
allow: this.validateIP(inputData?.ip),
|
|
246
|
+
reasons: this.validateIP(inputData?.ip) ? [] : ["INVALID"],
|
|
247
|
+
response: this.generateIPDataAnalysis(inputData?.ip)
|
|
341
248
|
};
|
|
342
|
-
this.clientId = clientId;
|
|
343
|
-
this.rateLimitManager = RateLimitManager.getInstance();
|
|
344
249
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return fallbackData;
|
|
378
|
-
}
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
const delay = this.config.retryDelay * Math.pow(2, attempt - 1);
|
|
382
|
-
console.warn(`[Dymo API] Attempt ${attempt} failed. Retrying in ${delay}ms...`);
|
|
383
|
-
await this.sleep(delay);
|
|
250
|
+
static generateIPDataAnalysis(ip) {
|
|
251
|
+
const isValid = this.validateIP(ip);
|
|
252
|
+
return {
|
|
253
|
+
valid: isValid,
|
|
254
|
+
type: isValid ? "IPv4" : "Invalid",
|
|
255
|
+
class: isValid ? "A" : "Unknown",
|
|
256
|
+
fraud: false,
|
|
257
|
+
ip: ip || "",
|
|
258
|
+
continent: "",
|
|
259
|
+
continentCode: "",
|
|
260
|
+
country: "",
|
|
261
|
+
countryCode: "",
|
|
262
|
+
region: "",
|
|
263
|
+
regionName: "",
|
|
264
|
+
city: "",
|
|
265
|
+
district: "",
|
|
266
|
+
zipCode: "",
|
|
267
|
+
lat: 0,
|
|
268
|
+
lon: 0,
|
|
269
|
+
timezone: "",
|
|
270
|
+
offset: 0,
|
|
271
|
+
currency: "",
|
|
272
|
+
isp: "",
|
|
273
|
+
org: "",
|
|
274
|
+
as: "",
|
|
275
|
+
asname: "",
|
|
276
|
+
mobile: false,
|
|
277
|
+
proxy: true,
|
|
278
|
+
hosting: false,
|
|
279
|
+
plugins: {
|
|
280
|
+
blocklist: false,
|
|
281
|
+
riskScore: 0
|
|
384
282
|
}
|
|
385
|
-
}
|
|
386
|
-
throw lastError;
|
|
387
|
-
}
|
|
388
|
-
shouldRetry(error) {
|
|
389
|
-
const statusCode = error.response?.status;
|
|
390
|
-
const isNetworkError = !error.response && error.code !== "ECONNABORTED";
|
|
391
|
-
const isServerError = statusCode && statusCode >= 500;
|
|
392
|
-
return isNetworkError || isServerError;
|
|
393
|
-
}
|
|
394
|
-
sleep(ms) {
|
|
395
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
396
|
-
}
|
|
397
|
-
getConfig() {
|
|
398
|
-
return { ...this.config };
|
|
399
|
-
}
|
|
400
|
-
getClientId() {
|
|
401
|
-
return this.clientId;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
class FallbackDataGenerator {
|
|
405
|
-
static generateFallbackData(method, inputData) {
|
|
406
|
-
switch (method) {
|
|
407
|
-
case "isValidData":
|
|
408
|
-
case "isValidDataRaw":
|
|
409
|
-
return this.generateDataValidationAnalysis(inputData);
|
|
410
|
-
case "isValidEmail":
|
|
411
|
-
return this.generateEmailValidatorResponse(inputData);
|
|
412
|
-
case "isValidIP":
|
|
413
|
-
return this.generateIPValidatorResponse(inputData);
|
|
414
|
-
case "isValidPhone":
|
|
415
|
-
return this.generatePhoneValidatorResponse(inputData);
|
|
416
|
-
case "protectReq":
|
|
417
|
-
return this.generateHTTPRequest(inputData);
|
|
418
|
-
case "sendEmail":
|
|
419
|
-
return this.generateEmailStatus();
|
|
420
|
-
case "getRandom":
|
|
421
|
-
return this.generateSRNSummary(inputData);
|
|
422
|
-
case "extractWithTextly":
|
|
423
|
-
return this.generateExtractWithTextly(inputData);
|
|
424
|
-
case "getPrayerTimes":
|
|
425
|
-
return this.generatePrayerTimes(inputData);
|
|
426
|
-
case "satinize":
|
|
427
|
-
case "satinizer":
|
|
428
|
-
return this.generateSatinizedInputAnalysis(inputData);
|
|
429
|
-
case "isValidPwd":
|
|
430
|
-
return this.generatePasswordValidationResult(inputData);
|
|
431
|
-
default:
|
|
432
|
-
throw new Error(`Unknown method for fallback: ${method}`);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
static validateURL(url) {
|
|
436
|
-
if (!url) return false;
|
|
437
|
-
const urlRegex = /^https?:\/\/(?:[-\w.])+(?:\:[0-9]+)?(?:\/(?:[\w\/_.])*(?:\?(?:[\w&=%.])*)?(?:\#(?:[\w.])*)?)?$/;
|
|
438
|
-
return urlRegex.test(url);
|
|
439
|
-
}
|
|
440
|
-
static validateEmail(email) {
|
|
441
|
-
if (!email) return false;
|
|
442
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
443
|
-
return emailRegex.test(email);
|
|
283
|
+
};
|
|
444
284
|
}
|
|
445
|
-
static
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
285
|
+
static generatePhoneValidatorResponse(inputData) {
|
|
286
|
+
return {
|
|
287
|
+
phone: inputData?.phone || "",
|
|
288
|
+
allow: this.validatePhone(inputData?.phone),
|
|
289
|
+
reasons: this.validatePhone(inputData?.phone) ? [] : ["INVALID"],
|
|
290
|
+
response: this.generatePhoneDataAnalysis(inputData?.phone)
|
|
291
|
+
};
|
|
451
292
|
}
|
|
452
|
-
static
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
293
|
+
static generatePhoneDataAnalysis(phone) {
|
|
294
|
+
const phoneNumber = phone?.phone || phone;
|
|
295
|
+
const isValid = this.validatePhone(phoneNumber);
|
|
296
|
+
return {
|
|
297
|
+
valid: isValid,
|
|
298
|
+
fraud: false,
|
|
299
|
+
phone: phone?.phone || "",
|
|
300
|
+
prefix: "",
|
|
301
|
+
number: "",
|
|
302
|
+
lineType: "Unknown",
|
|
303
|
+
carrierInfo: {
|
|
304
|
+
carrierName: "",
|
|
305
|
+
accuracy: 0,
|
|
306
|
+
carrierCountry: "",
|
|
307
|
+
carrierCountryCode: ""
|
|
308
|
+
},
|
|
309
|
+
country: "",
|
|
310
|
+
countryCode: "",
|
|
311
|
+
plugins: {
|
|
312
|
+
blocklist: false,
|
|
313
|
+
riskScore: 0
|
|
466
314
|
}
|
|
467
|
-
|
|
468
|
-
isEven = !isEven;
|
|
469
|
-
}
|
|
470
|
-
return sum % 10 === 0;
|
|
315
|
+
};
|
|
471
316
|
}
|
|
472
|
-
static
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
317
|
+
static generateHTTPRequest(inputData) {
|
|
318
|
+
return {
|
|
319
|
+
method: inputData?.method || "GET",
|
|
320
|
+
url: inputData?.url || "",
|
|
321
|
+
headers: inputData?.headers || {},
|
|
322
|
+
body: inputData?.body || null,
|
|
323
|
+
allow: false,
|
|
324
|
+
reasons: ["FRAUD"],
|
|
325
|
+
protected: true
|
|
326
|
+
};
|
|
477
327
|
}
|
|
478
|
-
static
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
328
|
+
static generateEmailStatus() {
|
|
329
|
+
return {
|
|
330
|
+
status: false,
|
|
331
|
+
error: "API unavailable - using fallback response"
|
|
332
|
+
};
|
|
482
333
|
}
|
|
483
|
-
static
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
334
|
+
static generateSRNSummary(inputData) {
|
|
335
|
+
const quantity = inputData?.quantity || 1;
|
|
336
|
+
const values = Array.from({ length: quantity }, () => ({
|
|
337
|
+
integer: 0,
|
|
338
|
+
float: 0
|
|
339
|
+
}));
|
|
340
|
+
return {
|
|
341
|
+
values,
|
|
342
|
+
executionTime: 0
|
|
343
|
+
};
|
|
488
344
|
}
|
|
489
|
-
static
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
345
|
+
static generateExtractWithTextly(inputData) {
|
|
346
|
+
return {
|
|
347
|
+
data: inputData?.data || "",
|
|
348
|
+
extracted: {},
|
|
349
|
+
error: "API unavailable - using fallback response"
|
|
350
|
+
};
|
|
493
351
|
}
|
|
494
|
-
static
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
} catch {
|
|
499
|
-
return "";
|
|
500
|
-
}
|
|
352
|
+
static generatePrayerTimes(inputData) {
|
|
353
|
+
return {
|
|
354
|
+
error: "API unavailable - using fallback response"
|
|
355
|
+
};
|
|
501
356
|
}
|
|
502
|
-
static
|
|
357
|
+
static generateSatinizedInputAnalysis(inputData) {
|
|
503
358
|
return {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
359
|
+
input: inputData?.input || "",
|
|
360
|
+
formats: {
|
|
361
|
+
ascii: false,
|
|
362
|
+
bitcoinAddress: false,
|
|
363
|
+
cLikeIdentifier: false,
|
|
364
|
+
coordinates: false,
|
|
365
|
+
crediCard: false,
|
|
366
|
+
date: false,
|
|
367
|
+
discordUsername: false,
|
|
368
|
+
doi: false,
|
|
369
|
+
domain: false,
|
|
370
|
+
e164Phone: false,
|
|
371
|
+
email: false,
|
|
372
|
+
emoji: false,
|
|
373
|
+
hanUnification: false,
|
|
374
|
+
hashtag: false,
|
|
375
|
+
hyphenWordBreak: false,
|
|
376
|
+
ipv6: false,
|
|
377
|
+
ip: false,
|
|
378
|
+
jiraTicket: false,
|
|
379
|
+
macAddress: false,
|
|
380
|
+
name: false,
|
|
381
|
+
number: false,
|
|
382
|
+
panFromGstin: false,
|
|
383
|
+
password: false,
|
|
384
|
+
port: false,
|
|
385
|
+
tel: false,
|
|
386
|
+
text: false,
|
|
387
|
+
semver: false,
|
|
388
|
+
ssn: false,
|
|
389
|
+
uuid: false,
|
|
390
|
+
url: false,
|
|
391
|
+
urlSlug: false,
|
|
392
|
+
username: false
|
|
522
393
|
},
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
394
|
+
includes: {
|
|
395
|
+
spaces: false,
|
|
396
|
+
hasSql: false,
|
|
397
|
+
hasNoSql: false,
|
|
398
|
+
letters: false,
|
|
399
|
+
uppercase: false,
|
|
400
|
+
lowercase: false,
|
|
401
|
+
symbols: false,
|
|
402
|
+
digits: false
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
static generatePasswordValidationResult(inputData) {
|
|
407
|
+
return {
|
|
408
|
+
valid: false,
|
|
409
|
+
password: inputData?.password || "",
|
|
410
|
+
details: [
|
|
411
|
+
{
|
|
412
|
+
validation: "length",
|
|
413
|
+
message: "API unavailable - using fallback response"
|
|
541
414
|
}
|
|
415
|
+
]
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const getPrayerTimes = async ({
|
|
420
|
+
axiosClient,
|
|
421
|
+
resilienceManager,
|
|
422
|
+
data
|
|
423
|
+
}) => {
|
|
424
|
+
const { lat, lon } = data;
|
|
425
|
+
if (lat === void 0 || lon === void 0) throw customError(1e3, "You must provide a latitude and longitude.");
|
|
426
|
+
if (resilienceManager) {
|
|
427
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("getPrayerTimes", data);
|
|
428
|
+
return await resilienceManager.executeWithResilience(
|
|
429
|
+
axiosClient,
|
|
430
|
+
{
|
|
431
|
+
method: "GET",
|
|
432
|
+
url: "/public/islam/prayertimes",
|
|
433
|
+
params: data
|
|
542
434
|
},
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
435
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
436
|
+
);
|
|
437
|
+
} else {
|
|
438
|
+
const response = await axiosClient.get("/public/islam/prayertimes", { params: data });
|
|
439
|
+
return response.data;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
const isValidPwd = async ({
|
|
443
|
+
axiosClient,
|
|
444
|
+
resilienceManager,
|
|
445
|
+
data
|
|
446
|
+
}) => {
|
|
447
|
+
let { email, password, bannedWords, min, max } = data;
|
|
448
|
+
if (password === void 0) throw customError(1e3, "You must specify at least the password.");
|
|
449
|
+
const params = { password: encodeURIComponent(password) };
|
|
450
|
+
if (email) {
|
|
451
|
+
if (!/^[a-zA-Z0-9._\-+]+@?[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(email)) throw customError(1500, "If you provide an email address it must be valid.");
|
|
452
|
+
params.email = encodeURIComponent(email);
|
|
453
|
+
}
|
|
454
|
+
if (bannedWords) {
|
|
455
|
+
if (typeof bannedWords === "string") bannedWords = bannedWords.slice(1, -1).trim().split(",").map((item) => item.trim());
|
|
456
|
+
if (!Array.isArray(bannedWords) || bannedWords.length > 10) throw customError(1500, "If you provide a list of banned words; the list may not exceed 10 words and must be of array type.");
|
|
457
|
+
if (!bannedWords.every((word) => typeof word === "string") || new Set(bannedWords).size !== bannedWords.length) throw customError(1500, "If you provide a list of banned words; all elements must be non-repeated strings.");
|
|
458
|
+
params.bannedWords = bannedWords;
|
|
459
|
+
}
|
|
460
|
+
if (min !== void 0 && (!Number.isInteger(min) || min < 8 || min > 32)) throw customError(1500, "If you provide a minimum it must be valid.");
|
|
461
|
+
if (max !== void 0 && (!Number.isInteger(max) || max < 32 || max > 100)) throw customError(1500, "If you provide a maximum it must be valid.");
|
|
462
|
+
if (min !== void 0) params.min = min;
|
|
463
|
+
if (max !== void 0) params.max = max;
|
|
464
|
+
if (resilienceManager) {
|
|
465
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("isValidPwd", data);
|
|
466
|
+
return await resilienceManager.executeWithResilience(
|
|
467
|
+
axiosClient,
|
|
468
|
+
{
|
|
469
|
+
method: "GET",
|
|
470
|
+
url: "/public/validPwd",
|
|
471
|
+
params
|
|
553
472
|
},
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
473
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
474
|
+
);
|
|
475
|
+
} else {
|
|
476
|
+
const response = await axiosClient.get("/public/validPwd", { params });
|
|
477
|
+
return response.data;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const satinize = async ({
|
|
481
|
+
axiosClient,
|
|
482
|
+
resilienceManager,
|
|
483
|
+
input
|
|
484
|
+
}) => {
|
|
485
|
+
if (input === void 0) throw customError(1e3, "You must specify at least the input.");
|
|
486
|
+
if (resilienceManager) {
|
|
487
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("satinize", input);
|
|
488
|
+
return await resilienceManager.executeWithResilience(
|
|
489
|
+
axiosClient,
|
|
490
|
+
{
|
|
491
|
+
method: "GET",
|
|
492
|
+
url: "/public/inputSatinizer",
|
|
493
|
+
params: { input: encodeURIComponent(input) }
|
|
494
|
+
},
|
|
495
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
496
|
+
);
|
|
497
|
+
} else {
|
|
498
|
+
const response = await axiosClient.get("/public/inputSatinizer", { params: { input: encodeURIComponent(input) } });
|
|
499
|
+
return response.data;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
const extractWithTextly = async ({
|
|
503
|
+
axiosClient,
|
|
504
|
+
resilienceManager,
|
|
505
|
+
data
|
|
506
|
+
}) => {
|
|
507
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
508
|
+
if (!data.data) throw customError(1500, "No data provided.");
|
|
509
|
+
if (!data.format) throw customError(1500, "No format provided.");
|
|
510
|
+
if (resilienceManager) {
|
|
511
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("extractWithTextly", data);
|
|
512
|
+
return await resilienceManager.executeWithResilience(
|
|
513
|
+
axiosClient,
|
|
514
|
+
{
|
|
515
|
+
method: "POST",
|
|
516
|
+
url: "/private/textly/extract",
|
|
517
|
+
data
|
|
565
518
|
},
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
519
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
520
|
+
);
|
|
521
|
+
} else {
|
|
522
|
+
const response = await axiosClient.post("/private/textly/extract", data, { headers: { "Content-Type": "application/json" } });
|
|
523
|
+
return response.data;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
const getRandom = async ({
|
|
527
|
+
axiosClient,
|
|
528
|
+
resilienceManager,
|
|
529
|
+
data
|
|
530
|
+
}) => {
|
|
531
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
532
|
+
if (data.min === void 0 || data.max === void 0) throw customError(1500, "Both 'min' and 'max' parameters must be defined.");
|
|
533
|
+
if (data.min >= data.max) throw customError(1500, "'min' must be less than 'max'.");
|
|
534
|
+
if (data.min < -1e9 || data.min > 1e9) throw customError(1500, "'min' must be an integer in the interval [-1000000000, 1000000000].");
|
|
535
|
+
if (data.max < -1e9 || data.max > 1e9) throw customError(1500, "'max' must be an integer in the interval [-1000000000, 1000000000].");
|
|
536
|
+
if (resilienceManager) {
|
|
537
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("getRandom", data);
|
|
538
|
+
return await resilienceManager.executeWithResilience(
|
|
539
|
+
axiosClient,
|
|
540
|
+
{
|
|
541
|
+
method: "POST",
|
|
542
|
+
url: "/private/srng",
|
|
543
|
+
data
|
|
576
544
|
},
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
545
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
546
|
+
);
|
|
547
|
+
} else {
|
|
548
|
+
const response = await axiosClient.post("/private/srng", data, { headers: { "Content-Type": "application/json" } });
|
|
549
|
+
return response.data;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
const isValidDataRaw = async ({
|
|
553
|
+
axiosClient,
|
|
554
|
+
resilienceManager,
|
|
555
|
+
data
|
|
556
|
+
}) => {
|
|
557
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
558
|
+
if (!Object.keys(data).some((key) => ["url", "email", "phone", "domain", "creditCard", "ip", "wallet", "userAgent", "iban"].includes(key) && data.hasOwnProperty(key))) throw customError(1500, "You must provide at least one parameter.");
|
|
559
|
+
if (resilienceManager) {
|
|
560
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("isValidDataRaw", data);
|
|
561
|
+
return await resilienceManager.executeWithResilience(
|
|
562
|
+
axiosClient,
|
|
563
|
+
{
|
|
564
|
+
method: "POST",
|
|
565
|
+
url: "/private/secure/verify",
|
|
566
|
+
data
|
|
567
|
+
},
|
|
568
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
569
|
+
);
|
|
570
|
+
} else {
|
|
571
|
+
const response = await axiosClient.post("/private/secure/verify", data, { headers: { "Content-Type": "application/json" } });
|
|
572
|
+
return response.data;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
const isValidEmail = async ({
|
|
576
|
+
axiosClient,
|
|
577
|
+
resilienceManager,
|
|
578
|
+
email,
|
|
579
|
+
rules
|
|
580
|
+
}) => {
|
|
581
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
582
|
+
if (rules.deny.length === 0) throw customError(1500, "You must provide at least one deny rule.");
|
|
583
|
+
if (rules.mode === "DRY_RUN") {
|
|
584
|
+
console.warn("[Dymo API] DRY_RUN mode is enabled. No requests with real data will be processed until you switch to LIVE mode.");
|
|
585
|
+
return {
|
|
586
|
+
email: typeof email === "string" ? email : "",
|
|
587
|
+
allow: true,
|
|
588
|
+
reasons: [],
|
|
589
|
+
response: "CHANGE TO LIVE MODE"
|
|
586
590
|
};
|
|
587
591
|
}
|
|
588
|
-
|
|
592
|
+
const plugins = [
|
|
593
|
+
rules.deny.includes("NO_MX_RECORDS") ? "mxRecords" : void 0,
|
|
594
|
+
rules.deny.includes("NO_REACHABLE") ? "reachable" : void 0,
|
|
595
|
+
rules.deny.includes("HIGH_RISK_SCORE") ? "riskScore" : void 0,
|
|
596
|
+
rules.deny.includes("NO_GRAVATAR") ? "gravatar" : void 0
|
|
597
|
+
].filter(Boolean);
|
|
598
|
+
let responseEmail;
|
|
599
|
+
if (resilienceManager) {
|
|
600
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("isValidEmail", email);
|
|
601
|
+
const response = await resilienceManager.executeWithResilience(
|
|
602
|
+
axiosClient,
|
|
603
|
+
{
|
|
604
|
+
method: "POST",
|
|
605
|
+
url: "/private/secure/verify",
|
|
606
|
+
data: { email, plugins }
|
|
607
|
+
},
|
|
608
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
609
|
+
);
|
|
610
|
+
responseEmail = response.email;
|
|
611
|
+
} else {
|
|
612
|
+
const response = await axiosClient.post("/private/secure/verify", { email, plugins }, { headers: { "Content-Type": "application/json" } });
|
|
613
|
+
responseEmail = response.data.email;
|
|
614
|
+
}
|
|
615
|
+
if (!responseEmail || !responseEmail.valid) {
|
|
589
616
|
return {
|
|
590
|
-
email:
|
|
591
|
-
allow:
|
|
592
|
-
reasons:
|
|
593
|
-
response:
|
|
617
|
+
email: responseEmail?.email || (typeof email === "string" ? email : ""),
|
|
618
|
+
allow: false,
|
|
619
|
+
reasons: ["INVALID"],
|
|
620
|
+
response: responseEmail
|
|
594
621
|
};
|
|
595
622
|
}
|
|
596
|
-
|
|
623
|
+
const reasons = [];
|
|
624
|
+
if (rules.deny.includes("FRAUD") && responseEmail.fraud) reasons.push("FRAUD");
|
|
625
|
+
if (rules.deny.includes("PROXIED_EMAIL") && responseEmail.proxiedEmail) reasons.push("PROXIED_EMAIL");
|
|
626
|
+
if (rules.deny.includes("FREE_SUBDOMAIN") && responseEmail.freeSubdomain) reasons.push("FREE_SUBDOMAIN");
|
|
627
|
+
if (rules.deny.includes("PERSONAL_EMAIL") && !responseEmail.corporate) reasons.push("PERSONAL_EMAIL");
|
|
628
|
+
if (rules.deny.includes("CORPORATE_EMAIL") && responseEmail.corporate) reasons.push("CORPORATE_EMAIL");
|
|
629
|
+
if (rules.deny.includes("NO_MX_RECORDS") && responseEmail.plugins?.mxRecords?.length === 0) reasons.push("NO_MX_RECORDS");
|
|
630
|
+
if (rules.deny.includes("NO_REPLY_EMAIL") && responseEmail.noReply) reasons.push("NO_REPLY_EMAIL");
|
|
631
|
+
if (rules.deny.includes("ROLE_ACCOUNT") && responseEmail.roleAccount) reasons.push("ROLE_ACCOUNT");
|
|
632
|
+
if (rules.deny.includes("NO_REACHABLE") && responseEmail.plugins?.reachable === false) reasons.push("NO_REACHABLE");
|
|
633
|
+
if (rules.deny.includes("HIGH_RISK_SCORE") && (responseEmail.plugins?.riskScore ?? 0) >= 80) reasons.push("HIGH_RISK_SCORE");
|
|
634
|
+
if (rules.deny.includes("NO_GRAVATAR") && !responseEmail.plugins?.gravatar) reasons.push("NO_GRAVATAR");
|
|
635
|
+
return {
|
|
636
|
+
email: responseEmail.email,
|
|
637
|
+
allow: reasons.length === 0,
|
|
638
|
+
reasons,
|
|
639
|
+
response: responseEmail
|
|
640
|
+
};
|
|
641
|
+
};
|
|
642
|
+
const isValidIP = async ({
|
|
643
|
+
axiosClient,
|
|
644
|
+
resilienceManager,
|
|
645
|
+
ip,
|
|
646
|
+
rules
|
|
647
|
+
}) => {
|
|
648
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
649
|
+
if (rules.deny.length === 0) throw customError(1500, "You must provide at least one deny rule.");
|
|
650
|
+
if (rules.mode === "DRY_RUN") {
|
|
651
|
+
console.warn("[Dymo API] DRY_RUN mode is enabled. No requests with real data will be processed until you switch to LIVE mode.");
|
|
597
652
|
return {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
corporate: false,
|
|
603
|
-
email: email || "",
|
|
604
|
-
realUser: "",
|
|
605
|
-
didYouMean: null,
|
|
606
|
-
noReply: false,
|
|
607
|
-
customTLD: false,
|
|
608
|
-
domain: "",
|
|
609
|
-
roleAccount: false,
|
|
610
|
-
plugins: {
|
|
611
|
-
mxRecords: [],
|
|
612
|
-
blocklist: false,
|
|
613
|
-
compromiseDetector: false,
|
|
614
|
-
nsfw: false,
|
|
615
|
-
reputation: "unknown",
|
|
616
|
-
riskScore: 0,
|
|
617
|
-
torNetwork: false,
|
|
618
|
-
typosquatting: 0,
|
|
619
|
-
urlShortener: false
|
|
620
|
-
}
|
|
653
|
+
ip: typeof ip === "string" ? ip : "",
|
|
654
|
+
allow: true,
|
|
655
|
+
reasons: [],
|
|
656
|
+
response: "CHANGE TO LIVE MODE"
|
|
621
657
|
};
|
|
622
658
|
}
|
|
623
|
-
|
|
659
|
+
const plugins = [
|
|
660
|
+
rules.deny.includes("TOR_NETWORK") ? "torNetwork" : void 0,
|
|
661
|
+
rules.deny.includes("HIGH_RISK_SCORE") ? "riskScore" : void 0
|
|
662
|
+
].filter(Boolean);
|
|
663
|
+
let responseIP;
|
|
664
|
+
if (resilienceManager) {
|
|
665
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("isValidIP", ip);
|
|
666
|
+
const response = await resilienceManager.executeWithResilience(
|
|
667
|
+
axiosClient,
|
|
668
|
+
{
|
|
669
|
+
method: "POST",
|
|
670
|
+
url: "/private/secure/verify",
|
|
671
|
+
data: { ip, plugins }
|
|
672
|
+
},
|
|
673
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
674
|
+
);
|
|
675
|
+
responseIP = response.ip;
|
|
676
|
+
} else {
|
|
677
|
+
const response = await axiosClient.post("/private/secure/verify", { ip, plugins }, { headers: { "Content-Type": "application/json" } });
|
|
678
|
+
responseIP = response.data.ip;
|
|
679
|
+
}
|
|
680
|
+
if (!responseIP || !responseIP.valid) {
|
|
624
681
|
return {
|
|
625
|
-
ip:
|
|
626
|
-
allow:
|
|
627
|
-
reasons:
|
|
628
|
-
response:
|
|
682
|
+
ip: responseIP?.ip || (typeof ip === "string" ? ip : ""),
|
|
683
|
+
allow: false,
|
|
684
|
+
reasons: ["INVALID"],
|
|
685
|
+
response: responseIP
|
|
629
686
|
};
|
|
630
687
|
}
|
|
631
|
-
|
|
632
|
-
|
|
688
|
+
const reasons = [];
|
|
689
|
+
if (rules.deny.includes("FRAUD") && responseIP.fraud) reasons.push("FRAUD");
|
|
690
|
+
if (rules.deny.includes("TOR_NETWORK") && responseIP.plugins?.torNetwork) reasons.push("TOR_NETWORK");
|
|
691
|
+
if (rules.deny.includes("HIGH_RISK_SCORE") && (responseIP.plugins?.riskScore ?? 0) >= 80) reasons.push("HIGH_RISK_SCORE");
|
|
692
|
+
for (const rule of rules.deny) {
|
|
693
|
+
if (rule.startsWith("COUNTRY:")) {
|
|
694
|
+
const block = rule.split(":")[1];
|
|
695
|
+
if (responseIP.countryCode === block) reasons.push(`COUNTRY:${block}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
ip: responseIP.ip,
|
|
700
|
+
allow: reasons.length === 0,
|
|
701
|
+
reasons,
|
|
702
|
+
response: responseIP
|
|
703
|
+
};
|
|
704
|
+
};
|
|
705
|
+
const isValidPhone = async ({
|
|
706
|
+
axiosClient,
|
|
707
|
+
resilienceManager,
|
|
708
|
+
phone,
|
|
709
|
+
rules
|
|
710
|
+
}) => {
|
|
711
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
712
|
+
if (rules.deny.length === 0) throw customError(1500, "You must provide at least one deny rule.");
|
|
713
|
+
if (rules.mode === "DRY_RUN") {
|
|
714
|
+
console.warn("[Dymo API] DRY_RUN mode is enabled. No requests with real data will be processed until you switch to LIVE mode.");
|
|
633
715
|
return {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
ip: ip || "",
|
|
639
|
-
continent: "",
|
|
640
|
-
continentCode: "",
|
|
641
|
-
country: "",
|
|
642
|
-
countryCode: "",
|
|
643
|
-
region: "",
|
|
644
|
-
regionName: "",
|
|
645
|
-
city: "",
|
|
646
|
-
district: "",
|
|
647
|
-
zipCode: "",
|
|
648
|
-
lat: 0,
|
|
649
|
-
lon: 0,
|
|
650
|
-
timezone: "",
|
|
651
|
-
offset: 0,
|
|
652
|
-
currency: "",
|
|
653
|
-
isp: "",
|
|
654
|
-
org: "",
|
|
655
|
-
as: "",
|
|
656
|
-
asname: "",
|
|
657
|
-
mobile: false,
|
|
658
|
-
proxy: true,
|
|
659
|
-
hosting: false,
|
|
660
|
-
plugins: {
|
|
661
|
-
blocklist: false,
|
|
662
|
-
riskScore: 0
|
|
663
|
-
}
|
|
716
|
+
phone: typeof phone === "string" ? phone : "",
|
|
717
|
+
allow: true,
|
|
718
|
+
reasons: [],
|
|
719
|
+
response: "CHANGE TO LIVE MODE"
|
|
664
720
|
};
|
|
665
721
|
}
|
|
666
|
-
|
|
722
|
+
const plugins = [
|
|
723
|
+
rules.deny.includes("HIGH_RISK_SCORE") ? "riskScore" : void 0
|
|
724
|
+
].filter(Boolean);
|
|
725
|
+
let responsePhone;
|
|
726
|
+
if (resilienceManager) {
|
|
727
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("isValidPhone", phone);
|
|
728
|
+
const response = await resilienceManager.executeWithResilience(
|
|
729
|
+
axiosClient,
|
|
730
|
+
{
|
|
731
|
+
method: "POST",
|
|
732
|
+
url: "/private/secure/verify",
|
|
733
|
+
data: { phone, plugins }
|
|
734
|
+
},
|
|
735
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
736
|
+
);
|
|
737
|
+
responsePhone = response.phone;
|
|
738
|
+
} else {
|
|
739
|
+
const response = await axiosClient.post("/private/secure/verify", { phone, plugins }, { headers: { "Content-Type": "application/json" } });
|
|
740
|
+
responsePhone = response.data.phone;
|
|
741
|
+
}
|
|
742
|
+
if (!responsePhone || !responsePhone.valid) {
|
|
667
743
|
return {
|
|
668
|
-
phone:
|
|
669
|
-
allow:
|
|
670
|
-
reasons:
|
|
671
|
-
response:
|
|
744
|
+
phone: responsePhone?.phone || (typeof phone === "string" ? phone : ""),
|
|
745
|
+
allow: false,
|
|
746
|
+
reasons: ["INVALID"],
|
|
747
|
+
response: responsePhone
|
|
672
748
|
};
|
|
673
749
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
750
|
+
const reasons = [];
|
|
751
|
+
if (rules.deny.includes("FRAUD") && responsePhone.fraud) reasons.push("FRAUD");
|
|
752
|
+
if (rules.deny.includes("HIGH_RISK_SCORE") && (responsePhone.plugins?.riskScore ?? 0) >= 80) reasons.push("HIGH_RISK_SCORE");
|
|
753
|
+
for (const rule of rules.deny) {
|
|
754
|
+
if (rule.startsWith("COUNTRY:")) {
|
|
755
|
+
const block = rule.split(":")[1];
|
|
756
|
+
if (responsePhone.countryCode === block) reasons.push(`COUNTRY:${block}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
phone: responsePhone.phone,
|
|
761
|
+
allow: reasons.length === 0,
|
|
762
|
+
reasons,
|
|
763
|
+
response: responsePhone
|
|
764
|
+
};
|
|
765
|
+
};
|
|
766
|
+
const getUserAgent = (req) => {
|
|
767
|
+
return req.headers?.["user-agent"] || req.headers?.["User-Agent"];
|
|
768
|
+
};
|
|
769
|
+
const getIp = (req) => {
|
|
770
|
+
return req.ip || req.headers?.["x-forwarded-for"] || req.connection?.remoteAddress || req.socket?.remoteAddress || req.req?.socket?.remoteAddress;
|
|
771
|
+
};
|
|
772
|
+
const handleRequest = (req) => {
|
|
773
|
+
return {
|
|
774
|
+
body: req.body,
|
|
775
|
+
userAgent: getUserAgent(req),
|
|
776
|
+
ip: getIp(req)
|
|
777
|
+
};
|
|
778
|
+
};
|
|
779
|
+
const protectReq = async ({
|
|
780
|
+
axiosClient,
|
|
781
|
+
resilienceManager,
|
|
782
|
+
req,
|
|
783
|
+
rules
|
|
784
|
+
}) => {
|
|
785
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
786
|
+
const reqData = handleRequest(req);
|
|
787
|
+
if (!reqData.userAgent || !reqData.ip) throw customError(1500, "You must provide user agent and ip.");
|
|
788
|
+
if (rules.mode === "DRY_RUN") {
|
|
789
|
+
console.warn("[Dymo API] DRY_RUN mode is enabled. No requests with real data will be processed until you switch to LIVE mode.");
|
|
677
790
|
return {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
791
|
+
ip: reqData.ip,
|
|
792
|
+
userAgent: reqData.userAgent,
|
|
793
|
+
allow: true,
|
|
794
|
+
reasons: []
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
const requestData = {
|
|
798
|
+
ip: reqData.ip,
|
|
799
|
+
userAgent: reqData.userAgent,
|
|
800
|
+
allowBots: rules.allowBots,
|
|
801
|
+
deny: rules.deny
|
|
802
|
+
};
|
|
803
|
+
if (resilienceManager) {
|
|
804
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("protectReq", req);
|
|
805
|
+
return await resilienceManager.executeWithResilience(
|
|
806
|
+
axiosClient,
|
|
807
|
+
{
|
|
808
|
+
method: "POST",
|
|
809
|
+
url: "/private/waf/verifyRequest",
|
|
810
|
+
data: requestData
|
|
811
|
+
},
|
|
812
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
813
|
+
);
|
|
814
|
+
} else {
|
|
815
|
+
const response = await axiosClient.post("/private/waf/verifyRequest", requestData, { headers: { "Content-Type": "application/json" } });
|
|
816
|
+
return response.data;
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
const fs = {};
|
|
820
|
+
const convertTailwindToInlineCss = (htmlContent) => {
|
|
821
|
+
return htmlContent.replace(/class="([^"]+)"( style="([^"]+)")?/g, (match, classList, _, existingStyle) => {
|
|
822
|
+
const compiledStyles = twToCss.twi(classList, { minify: true, merge: true });
|
|
823
|
+
return match.replace(/class="[^"]+"/, "").replace(/ style="[^"]+"/, "").concat(` style="${existingStyle ? `${existingStyle.trim().slice(0, -1)}; ${compiledStyles}` : compiledStyles}"`);
|
|
824
|
+
});
|
|
825
|
+
};
|
|
826
|
+
const sendEmail = async ({
|
|
827
|
+
axiosClient,
|
|
828
|
+
resilienceManager,
|
|
829
|
+
data
|
|
830
|
+
}) => {
|
|
831
|
+
if (!axiosClient.defaults.headers?.Authorization) throw customError(3e3, "Invalid private token.");
|
|
832
|
+
if (!data.from) throw customError(1500, "You must provide an email address from which the following will be sent.");
|
|
833
|
+
if (!data.to) throw customError(1500, "You must provide an email to be sent to.");
|
|
834
|
+
if (!data.subject) throw customError(1500, "You must provide a subject for the email to be sent.");
|
|
835
|
+
if (!data.html && !data.react && !React.isValidElement(data.react)) throw customError(1500, "You must provide HTML or a React component.");
|
|
836
|
+
if (data.html && data.react) throw customError(1500, "You must provide only HTML or a React component, not both.");
|
|
837
|
+
try {
|
|
838
|
+
if (data.react) {
|
|
839
|
+
data.html = await render.render(data.react);
|
|
840
|
+
delete data.react;
|
|
841
|
+
}
|
|
842
|
+
if (data.options && data.options.composeTailwindClasses) {
|
|
843
|
+
data.html = convertTailwindToInlineCss(data.html);
|
|
844
|
+
delete data.options.composeTailwindClasses;
|
|
845
|
+
}
|
|
846
|
+
} catch (error) {
|
|
847
|
+
throw customError(1500, `An error occurred while rendering your React component. Details: ${error}`);
|
|
848
|
+
}
|
|
849
|
+
let totalSize = 0;
|
|
850
|
+
if (data.attachments && Array.isArray(data.attachments)) {
|
|
851
|
+
const processedAttachments = await Promise.all(
|
|
852
|
+
data.attachments.map(async (attachment) => {
|
|
853
|
+
if (attachment.path && attachment.content || !attachment.path && !attachment.content) throw customError(1500, "You must provide either 'path' or 'content', not both.");
|
|
854
|
+
let contentBuffer;
|
|
855
|
+
if (attachment.path) contentBuffer = await fs.readFile(path.resolve(attachment.path));
|
|
856
|
+
else if (attachment.content) contentBuffer = attachment.content instanceof Buffer ? attachment.content : Buffer.from(attachment.content);
|
|
857
|
+
totalSize += Buffer.byteLength(contentBuffer);
|
|
858
|
+
if (totalSize > 40 * 1024 * 1024) throw customError(1500, "Attachments exceed the maximum allowed size of 40 MB.");
|
|
859
|
+
return {
|
|
860
|
+
filename: attachment.filename || path.basename(attachment.path || ""),
|
|
861
|
+
content: contentBuffer,
|
|
862
|
+
cid: attachment.cid || attachment.filename
|
|
863
|
+
};
|
|
864
|
+
})
|
|
865
|
+
);
|
|
866
|
+
data.attachments = processedAttachments;
|
|
867
|
+
}
|
|
868
|
+
if (resilienceManager) {
|
|
869
|
+
const fallbackData = FallbackDataGenerator.generateFallbackData("sendEmail", data);
|
|
870
|
+
return await resilienceManager.executeWithResilience(
|
|
871
|
+
axiosClient,
|
|
872
|
+
{
|
|
873
|
+
method: "POST",
|
|
874
|
+
url: "/private/sender/sendEmail",
|
|
875
|
+
data
|
|
689
876
|
},
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
};
|
|
877
|
+
resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
878
|
+
);
|
|
879
|
+
} else {
|
|
880
|
+
const response = await axiosClient.post("/private/sender/sendEmail", data);
|
|
881
|
+
return response.data;
|
|
697
882
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
headers: inputData?.headers || {},
|
|
703
|
-
body: inputData?.body || null,
|
|
704
|
-
allow: false,
|
|
705
|
-
reasons: ["FRAUD"],
|
|
706
|
-
protected: true
|
|
707
|
-
};
|
|
883
|
+
};
|
|
884
|
+
class RateLimitManager {
|
|
885
|
+
constructor() {
|
|
886
|
+
this.tracker = {};
|
|
708
887
|
}
|
|
709
|
-
static
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
error: "API unavailable - using fallback response"
|
|
713
|
-
};
|
|
888
|
+
static getInstance() {
|
|
889
|
+
if (!RateLimitManager.instance) RateLimitManager.instance = new RateLimitManager();
|
|
890
|
+
return RateLimitManager.instance;
|
|
714
891
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
return
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
892
|
+
/**
|
|
893
|
+
* Parses a header value that could be a number or "unlimited".
|
|
894
|
+
* Returns undefined if the value is "unlimited", null, undefined, or invalid.
|
|
895
|
+
*/
|
|
896
|
+
parseHeaderValue(value) {
|
|
897
|
+
if (value === null || value === void 0) return void 0;
|
|
898
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
899
|
+
const strValue = String(value).trim().toLowerCase();
|
|
900
|
+
if (!strValue || strValue === "unlimited") return void 0;
|
|
901
|
+
const parsed = parseInt(strValue, 10);
|
|
902
|
+
return isNaN(parsed) || parsed < 0 ? void 0 : parsed;
|
|
725
903
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
904
|
+
updateRateLimit(clientId, headers) {
|
|
905
|
+
if (!this.tracker[clientId]) this.tracker[clientId] = {};
|
|
906
|
+
const limitInfo = this.tracker[clientId];
|
|
907
|
+
const limitRequests = headers["x-ratelimit-limit-requests"];
|
|
908
|
+
const remainingRequests = headers["x-ratelimit-remaining-requests"];
|
|
909
|
+
const resetRequests = headers["x-ratelimit-reset-requests"];
|
|
910
|
+
const retryAfter = headers["retry-after"];
|
|
911
|
+
const parsedLimit = this.parseHeaderValue(limitRequests);
|
|
912
|
+
const parsedRemaining = this.parseHeaderValue(remainingRequests);
|
|
913
|
+
const parsedRetryAfter = this.parseHeaderValue(retryAfter);
|
|
914
|
+
if (parsedLimit !== void 0) limitInfo.limit = parsedLimit;
|
|
915
|
+
if (parsedRemaining !== void 0) limitInfo.remaining = parsedRemaining;
|
|
916
|
+
if (typeof remainingRequests === "string" && remainingRequests.trim().toLowerCase() === "unlimited") limitInfo.isUnlimited = true;
|
|
917
|
+
if (resetRequests && typeof resetRequests === "string") limitInfo.resetTime = resetRequests;
|
|
918
|
+
if (parsedRetryAfter !== void 0) limitInfo.retryAfter = parsedRetryAfter;
|
|
919
|
+
limitInfo.lastUpdated = Date.now();
|
|
732
920
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
921
|
+
isRateLimited(clientId) {
|
|
922
|
+
const limitInfo = this.tracker[clientId];
|
|
923
|
+
if (!limitInfo) return false;
|
|
924
|
+
if (limitInfo.isUnlimited) return false;
|
|
925
|
+
return limitInfo.remaining !== void 0 && limitInfo.remaining <= 0;
|
|
737
926
|
}
|
|
738
|
-
|
|
739
|
-
return
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
hyphenWordBreak: false,
|
|
757
|
-
ipv6: false,
|
|
758
|
-
ip: false,
|
|
759
|
-
jiraTicket: false,
|
|
760
|
-
macAddress: false,
|
|
761
|
-
name: false,
|
|
762
|
-
number: false,
|
|
763
|
-
panFromGstin: false,
|
|
764
|
-
password: false,
|
|
765
|
-
port: false,
|
|
766
|
-
tel: false,
|
|
767
|
-
text: false,
|
|
768
|
-
semver: false,
|
|
769
|
-
ssn: false,
|
|
770
|
-
uuid: false,
|
|
771
|
-
url: false,
|
|
772
|
-
urlSlug: false,
|
|
773
|
-
username: false
|
|
774
|
-
},
|
|
775
|
-
includes: {
|
|
776
|
-
spaces: false,
|
|
777
|
-
hasSql: false,
|
|
778
|
-
hasNoSql: false,
|
|
779
|
-
letters: false,
|
|
780
|
-
uppercase: false,
|
|
781
|
-
lowercase: false,
|
|
782
|
-
symbols: false,
|
|
783
|
-
digits: false
|
|
784
|
-
}
|
|
927
|
+
getRetryAfter(clientId) {
|
|
928
|
+
return this.tracker[clientId]?.retryAfter;
|
|
929
|
+
}
|
|
930
|
+
clearExpiredLimits() {
|
|
931
|
+
const now = Date.now();
|
|
932
|
+
Object.keys(this.tracker).forEach((clientId) => {
|
|
933
|
+
const limitInfo = this.tracker[clientId];
|
|
934
|
+
if (limitInfo.lastUpdated && now - limitInfo.lastUpdated > 3e5) delete this.tracker[clientId];
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
class ResilienceManager {
|
|
939
|
+
constructor(config2 = {}, clientId = "default") {
|
|
940
|
+
this.config = {
|
|
941
|
+
fallbackEnabled: config2.fallbackEnabled ?? false,
|
|
942
|
+
retryAttempts: Math.max(0, config2.retryAttempts ?? 2),
|
|
943
|
+
// Number of additional retries
|
|
944
|
+
retryDelay: Math.max(0, config2.retryDelay ?? 1e3)
|
|
785
945
|
};
|
|
946
|
+
this.clientId = clientId;
|
|
947
|
+
this.rateLimitManager = RateLimitManager.getInstance();
|
|
786
948
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
949
|
+
async executeWithResilience(axiosClient, requestConfig, fallbackData) {
|
|
950
|
+
let lastError;
|
|
951
|
+
const totalAttempts = 1 + this.config.retryAttempts;
|
|
952
|
+
this.rateLimitManager.clearExpiredLimits();
|
|
953
|
+
if (this.rateLimitManager.isRateLimited(this.clientId)) {
|
|
954
|
+
const retryAfter = this.rateLimitManager.getRetryAfter(this.clientId);
|
|
955
|
+
if (retryAfter) {
|
|
956
|
+
console.warn(`[Dymo API] Client ${this.clientId} is rate limited. Waiting ${retryAfter} seconds...`);
|
|
957
|
+
await this.sleep(retryAfter * 1e3);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
for (let attempt = 1; attempt <= totalAttempts; attempt++) {
|
|
961
|
+
try {
|
|
962
|
+
const response = await axiosClient.request(requestConfig);
|
|
963
|
+
this.rateLimitManager.updateRateLimit(this.clientId, response.headers);
|
|
964
|
+
if (response.status === 429) {
|
|
965
|
+
const retryAfter = this.rateLimitManager.getRetryAfter(this.clientId);
|
|
966
|
+
if (retryAfter) {
|
|
967
|
+
console.warn(`[Dymo API] Rate limited. Waiting ${retryAfter} seconds (no retries)`);
|
|
968
|
+
await this.sleep(retryAfter * 1e3);
|
|
969
|
+
}
|
|
970
|
+
throw new Error(`Rate limited (429) - not retrying`);
|
|
795
971
|
}
|
|
796
|
-
|
|
797
|
-
|
|
972
|
+
return response.data;
|
|
973
|
+
} catch (error) {
|
|
974
|
+
lastError = error;
|
|
975
|
+
let shouldRetry = this.shouldRetry(error);
|
|
976
|
+
const isLastAttempt = attempt === totalAttempts;
|
|
977
|
+
if (error.response?.status === 429) shouldRetry = false;
|
|
978
|
+
if (!shouldRetry || isLastAttempt) {
|
|
979
|
+
if (this.config.fallbackEnabled && fallbackData) {
|
|
980
|
+
console.warn(`[Dymo API] Request failed after ${attempt} attempts. Using fallback data.`);
|
|
981
|
+
return fallbackData;
|
|
982
|
+
}
|
|
983
|
+
throw error;
|
|
984
|
+
}
|
|
985
|
+
const delay = this.config.retryDelay * Math.pow(2, attempt - 1);
|
|
986
|
+
console.warn(`[Dymo API] Attempt ${attempt} failed. Retrying in ${delay}ms...`);
|
|
987
|
+
await this.sleep(delay);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
throw lastError;
|
|
991
|
+
}
|
|
992
|
+
shouldRetry(error) {
|
|
993
|
+
const statusCode = error.response?.status;
|
|
994
|
+
const isNetworkError = !error.response && error.code !== "ECONNABORTED";
|
|
995
|
+
const isServerError = statusCode && statusCode >= 500;
|
|
996
|
+
return isNetworkError || isServerError;
|
|
997
|
+
}
|
|
998
|
+
sleep(ms) {
|
|
999
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1000
|
+
}
|
|
1001
|
+
getConfig() {
|
|
1002
|
+
return { ...this.config };
|
|
1003
|
+
}
|
|
1004
|
+
getClientId() {
|
|
1005
|
+
return this.clientId;
|
|
798
1006
|
}
|
|
799
1007
|
}
|
|
800
1008
|
class DymoAPI {
|
|
801
1009
|
/**
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1010
|
+
* @param {Object} options - Options to create the DymoAPI instance.
|
|
1011
|
+
* @param {string} [options.rootApiKey] - The root API key.
|
|
1012
|
+
* @param {string} [options.apiKey] - The API key.
|
|
1013
|
+
* @param {string} [options.baseUrl] - Whether to use a local server instead of the cloud server.
|
|
1014
|
+
* @param {Object} [options.serverEmailConfig] - The server email config.
|
|
1015
|
+
* @param {Object} [options.rules] - The rules.
|
|
1016
|
+
* @param {Object} [options.resilience] - The resilience config.
|
|
1017
|
+
* @description
|
|
1018
|
+
* This is the main class to interact with the Dymo API. It should be
|
|
1019
|
+
* instantiated with the root API key and the API key. The root API key is
|
|
1020
|
+
* used to fetch the tokens and the API key is used to authenticate the
|
|
1021
|
+
* requests. Requests are retried once by default with exponential backoff.
|
|
1022
|
+
* @example
|
|
1023
|
+
* const dymoApi = new DymoAPI({
|
|
1024
|
+
* rootApiKey: "6bfb7675-6b69-4f8d-9f43-5a6f7f02c6c5",
|
|
1025
|
+
* apiKey: "dm_4c8b7675-6b69-4f8d-9f43-5a6f7f02c6c5"
|
|
1026
|
+
* });
|
|
1027
|
+
*/
|
|
820
1028
|
constructor({
|
|
821
1029
|
rootApiKey = null,
|
|
822
1030
|
apiKey = null,
|
|
@@ -844,19 +1052,11 @@ class DymoAPI {
|
|
|
844
1052
|
headers: {
|
|
845
1053
|
"User-Agent": "DymoAPISDK/1.0.0",
|
|
846
1054
|
"X-Dymo-SDK-Env": "Node",
|
|
847
|
-
"X-Dymo-SDK-Version": "1.2.
|
|
1055
|
+
"X-Dymo-SDK-Version": "1.2.39"
|
|
848
1056
|
}
|
|
849
1057
|
});
|
|
850
1058
|
if (this.rootApiKey || this.apiKey) this.axiosClient.defaults.headers.Authorization = `Bearer ${this.rootApiKey || this.apiKey}`;
|
|
851
1059
|
}
|
|
852
|
-
getEmailPlugins(rules) {
|
|
853
|
-
return [
|
|
854
|
-
rules.deny.includes("NO_MX_RECORDS") ? "mxRecords" : void 0,
|
|
855
|
-
rules.deny.includes("NO_REACHABLE") ? "reachable" : void 0,
|
|
856
|
-
rules.deny.includes("HIGH_RISK_SCORE") ? "riskScore" : void 0,
|
|
857
|
-
rules.deny.includes("NO_GRAVATAR") ? "gravatar" : void 0
|
|
858
|
-
].filter(Boolean);
|
|
859
|
-
}
|
|
860
1060
|
// FUNCTIONS / Private.
|
|
861
1061
|
/**
|
|
862
1062
|
* Validates the given data against the configured validation settings.
|
|
@@ -882,16 +1082,7 @@ class DymoAPI {
|
|
|
882
1082
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/data-verifier)
|
|
883
1083
|
*/
|
|
884
1084
|
async isValidData(data) {
|
|
885
|
-
|
|
886
|
-
return await this.resilienceManager.executeWithResilience(
|
|
887
|
-
this.axiosClient,
|
|
888
|
-
{
|
|
889
|
-
method: "POST",
|
|
890
|
-
url: "/private/secure/verify",
|
|
891
|
-
data
|
|
892
|
-
},
|
|
893
|
-
this.resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
894
|
-
);
|
|
1085
|
+
return await isValidDataRaw({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, data });
|
|
895
1086
|
}
|
|
896
1087
|
/**
|
|
897
1088
|
* Validates the given data against the configured validation settings.
|
|
@@ -916,16 +1107,7 @@ class DymoAPI {
|
|
|
916
1107
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/data-verifier)
|
|
917
1108
|
*/
|
|
918
1109
|
async isValidDataRaw(data) {
|
|
919
|
-
|
|
920
|
-
return await this.resilienceManager.executeWithResilience(
|
|
921
|
-
this.axiosClient,
|
|
922
|
-
{
|
|
923
|
-
method: "POST",
|
|
924
|
-
url: "/private/secure/verify",
|
|
925
|
-
data
|
|
926
|
-
},
|
|
927
|
-
this.resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
928
|
-
);
|
|
1110
|
+
return await isValidDataRaw({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, data });
|
|
929
1111
|
}
|
|
930
1112
|
/**
|
|
931
1113
|
* Validates the given email against the configured rules.
|
|
@@ -942,47 +1124,11 @@ class DymoAPI {
|
|
|
942
1124
|
*
|
|
943
1125
|
* @example
|
|
944
1126
|
* const valid = await dymoClient.isValidEmail("user@example.com", { deny: ["FRAUD", "NO_MX_RECORDS"] });
|
|
945
|
-
*
|
|
1127
|
+
*
|
|
946
1128
|
* @see [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/email-validation)
|
|
947
1129
|
*/
|
|
948
1130
|
async isValidEmail(email, rules = this.rules.email) {
|
|
949
|
-
|
|
950
|
-
const response = await this.resilienceManager.executeWithResilience(
|
|
951
|
-
this.axiosClient,
|
|
952
|
-
{
|
|
953
|
-
method: "POST",
|
|
954
|
-
url: "/private/secure/verify",
|
|
955
|
-
data: { email, plugins: this.getEmailPlugins(rules) }
|
|
956
|
-
},
|
|
957
|
-
this.resilienceManager.getConfig().fallbackEnabled ? fallbackData : void 0
|
|
958
|
-
);
|
|
959
|
-
const responseEmail = response.email;
|
|
960
|
-
if (!responseEmail || !responseEmail.valid) {
|
|
961
|
-
return {
|
|
962
|
-
email: responseEmail?.email || (typeof email === "string" ? email : ""),
|
|
963
|
-
allow: false,
|
|
964
|
-
reasons: ["INVALID"],
|
|
965
|
-
response: responseEmail
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
const reasons = [];
|
|
969
|
-
if (rules.deny.includes("FRAUD") && responseEmail.fraud) reasons.push("FRAUD");
|
|
970
|
-
if (rules.deny.includes("PROXIED_EMAIL") && responseEmail.proxiedEmail) reasons.push("PROXIED_EMAIL");
|
|
971
|
-
if (rules.deny.includes("FREE_SUBDOMAIN") && responseEmail.freeSubdomain) reasons.push("FREE_SUBDOMAIN");
|
|
972
|
-
if (rules.deny.includes("PERSONAL_EMAIL") && !responseEmail.corporate) reasons.push("PERSONAL_EMAIL");
|
|
973
|
-
if (rules.deny.includes("CORPORATE_EMAIL") && responseEmail.corporate) reasons.push("CORPORATE_EMAIL");
|
|
974
|
-
if (rules.deny.includes("NO_MX_RECORDS") && responseEmail.plugins?.mxRecords?.length === 0) reasons.push("NO_MX_RECORDS");
|
|
975
|
-
if (rules.deny.includes("NO_REPLY_EMAIL") && responseEmail.noReply) reasons.push("NO_REPLY_EMAIL");
|
|
976
|
-
if (rules.deny.includes("ROLE_ACCOUNT") && responseEmail.roleAccount) reasons.push("ROLE_ACCOUNT");
|
|
977
|
-
if (rules.deny.includes("NO_REACHABLE") && responseEmail.plugins?.reachable === false) reasons.push("NO_REACHABLE");
|
|
978
|
-
if (rules.deny.includes("HIGH_RISK_SCORE") && (responseEmail.plugins?.riskScore ?? 0) >= 80) reasons.push("HIGH_RISK_SCORE");
|
|
979
|
-
if (rules.deny.includes("NO_GRAVATAR") && !responseEmail.plugins?.gravatar) reasons.push("NO_GRAVATAR");
|
|
980
|
-
return {
|
|
981
|
-
email: responseEmail.email,
|
|
982
|
-
allow: reasons.length === 0,
|
|
983
|
-
reasons,
|
|
984
|
-
response: responseEmail
|
|
985
|
-
};
|
|
1131
|
+
return await isValidEmail({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, email, rules });
|
|
986
1132
|
}
|
|
987
1133
|
/**
|
|
988
1134
|
* Validates the given IP against the configured rules.
|
|
@@ -991,7 +1137,7 @@ class DymoAPI {
|
|
|
991
1137
|
* If neither is set, it will throw an error.
|
|
992
1138
|
*
|
|
993
1139
|
* @param {string} [ip] - IP address to validate.
|
|
994
|
-
* @param {
|
|
1140
|
+
* @param {NegativeIPRules[]} [rules] - Optional rules for validation. Some rules are premium features.
|
|
995
1141
|
* @important
|
|
996
1142
|
* **⚠️ TOR_NETWORK and HIGH_RISK_SCORE are [PREMIUM](https://docs.tpeoficial.com/docs/dymo-api/private/data-verifier) features.**
|
|
997
1143
|
* @returns {Promise<Interfaces.IPValidatorResponse>} Resolves with the validation response.
|
|
@@ -999,14 +1145,14 @@ class DymoAPI {
|
|
|
999
1145
|
*
|
|
1000
1146
|
* @example
|
|
1001
1147
|
* const valid = await isValidIP("52.94.236.248", { deny: ["FRAUD", "TOR_NETWORK", "COUNTRY:RU"] });
|
|
1002
|
-
*
|
|
1148
|
+
*
|
|
1003
1149
|
* @see [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/ip-validation)
|
|
1004
1150
|
*/
|
|
1005
1151
|
async isValidIP(ip, rules = this.rules.ip) {
|
|
1006
|
-
return await isValidIP(this.axiosClient, ip, rules);
|
|
1152
|
+
return await isValidIP({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, ip, rules });
|
|
1007
1153
|
}
|
|
1008
1154
|
/**
|
|
1009
|
-
* Validates the given
|
|
1155
|
+
* Validates the given phone against the configured rules.
|
|
1010
1156
|
*
|
|
1011
1157
|
* This method requires either the root API key or the API key to be set.
|
|
1012
1158
|
* If neither is set, it will throw an error.
|
|
@@ -1015,16 +1161,16 @@ class DymoAPI {
|
|
|
1015
1161
|
* @param {NegativePhoneRules[]} [rules] - Optional rules for validation. Some rules are premium features.
|
|
1016
1162
|
* @important
|
|
1017
1163
|
* **⚠️ HIGH_RISK_SCORE is a [PREMIUM](https://docs.tpeoficial.com/docs/dymo-api/private/data-verifier) feature.**
|
|
1018
|
-
* @returns {Promise<Interfaces.
|
|
1164
|
+
* @returns {Promise<Interfaces.PhoneValidatorResponse>} Resolves with the validation response.
|
|
1019
1165
|
* @throws Will throw an error if validation cannot be performed.
|
|
1020
1166
|
*
|
|
1021
1167
|
* @example
|
|
1022
1168
|
* const valid = await dymoClient.isValidPhone("+34617509462", { deny: ["FRAUD", "INVALID"] });
|
|
1023
|
-
*
|
|
1169
|
+
*
|
|
1024
1170
|
* @see [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/phone-validation)
|
|
1025
1171
|
*/
|
|
1026
1172
|
async isValidPhone(phone, rules = this.rules.phone) {
|
|
1027
|
-
return await isValidPhone(this.axiosClient, phone, rules);
|
|
1173
|
+
return await isValidPhone({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, phone, rules });
|
|
1028
1174
|
}
|
|
1029
1175
|
/**
|
|
1030
1176
|
* Protects the given request against the configured rules.
|
|
@@ -1041,11 +1187,11 @@ class DymoAPI {
|
|
|
1041
1187
|
*
|
|
1042
1188
|
* @example
|
|
1043
1189
|
* const protectedReq = await dymoClient.protectReq(req);
|
|
1044
|
-
*
|
|
1190
|
+
*
|
|
1045
1191
|
* @see [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/data-verifier)
|
|
1046
1192
|
*/
|
|
1047
1193
|
async protectReq(req, rules = this.rules.waf) {
|
|
1048
|
-
return await protectReq(this.axiosClient, req, rules);
|
|
1194
|
+
return await protectReq({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, req, rules });
|
|
1049
1195
|
}
|
|
1050
1196
|
/**
|
|
1051
1197
|
* Sends an email using the configured email client settings.
|
|
@@ -1075,7 +1221,7 @@ class DymoAPI {
|
|
|
1075
1221
|
*/
|
|
1076
1222
|
async sendEmail(data) {
|
|
1077
1223
|
if (!this.serverEmailConfig && !this.rootApiKey) console.error(`[${config.lib.name}] You must configure the email client settings.`);
|
|
1078
|
-
return await sendEmail(this.axiosClient, { serverEmailConfig: this.serverEmailConfig, ...data });
|
|
1224
|
+
return await sendEmail({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, data: { serverEmailConfig: this.serverEmailConfig, ...data } });
|
|
1079
1225
|
}
|
|
1080
1226
|
/**
|
|
1081
1227
|
* Generates a random number between the provided min and max values.
|
|
@@ -1093,7 +1239,7 @@ class DymoAPI {
|
|
|
1093
1239
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/secure-random-number-generator)
|
|
1094
1240
|
*/
|
|
1095
1241
|
async getRandom(data) {
|
|
1096
|
-
return await getRandom(this.axiosClient, data);
|
|
1242
|
+
return await getRandom({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, data });
|
|
1097
1243
|
}
|
|
1098
1244
|
/**
|
|
1099
1245
|
* Extracts structured data from a given string using Textly.
|
|
@@ -1108,7 +1254,7 @@ class DymoAPI {
|
|
|
1108
1254
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/private/textly/text-extraction)
|
|
1109
1255
|
*/
|
|
1110
1256
|
async extractWithTextly(data) {
|
|
1111
|
-
return await extractWithTextly(this.axiosClient, data);
|
|
1257
|
+
return await extractWithTextly({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, data });
|
|
1112
1258
|
}
|
|
1113
1259
|
// FUNCTIONS / Public.
|
|
1114
1260
|
/**
|
|
@@ -1126,7 +1272,7 @@ class DymoAPI {
|
|
|
1126
1272
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/public/prayertimes)
|
|
1127
1273
|
*/
|
|
1128
1274
|
async getPrayerTimes(data) {
|
|
1129
|
-
return await getPrayerTimes(this.axiosClient, data);
|
|
1275
|
+
return await getPrayerTimes({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, data });
|
|
1130
1276
|
}
|
|
1131
1277
|
/**
|
|
1132
1278
|
* Satinizes the input, replacing any special characters with their HTML
|
|
@@ -1141,21 +1287,20 @@ class DymoAPI {
|
|
|
1141
1287
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/public/input-satinizer)
|
|
1142
1288
|
*/
|
|
1143
1289
|
async satinizer(data) {
|
|
1144
|
-
return await satinize(
|
|
1290
|
+
return await this.satinize(data.input);
|
|
1145
1291
|
}
|
|
1146
1292
|
/**
|
|
1147
1293
|
* Satinizes the input, replacing any special characters with their HTML
|
|
1148
1294
|
* entities.
|
|
1149
1295
|
*
|
|
1150
|
-
* @param {
|
|
1151
|
-
* @param {string} data.input - The input to be satinized.
|
|
1296
|
+
* @param {string} input - The input to be satinized.
|
|
1152
1297
|
* @returns {Promise<Interfaces.SatinizedInputAnalysis>} A promise that resolves to the response from the server.
|
|
1153
1298
|
* @throws Will throw an error if there is an issue with the satinization process.
|
|
1154
1299
|
*
|
|
1155
1300
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/public/input-satinizer)
|
|
1156
1301
|
*/
|
|
1157
1302
|
async satinize(input) {
|
|
1158
|
-
return await satinize(this.axiosClient, input);
|
|
1303
|
+
return await satinize({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, input });
|
|
1159
1304
|
}
|
|
1160
1305
|
/**
|
|
1161
1306
|
* Validates a password based on the given parameters.
|
|
@@ -1183,7 +1328,7 @@ class DymoAPI {
|
|
|
1183
1328
|
* [Documentation](https://docs.tpeoficial.com/docs/dymo-api/public/password-validator)
|
|
1184
1329
|
*/
|
|
1185
1330
|
async isValidPwd(data) {
|
|
1186
|
-
return await isValidPwd(this.axiosClient, data);
|
|
1331
|
+
return await isValidPwd({ axiosClient: this.axiosClient, resilienceManager: this.resilienceManager, data });
|
|
1187
1332
|
}
|
|
1188
1333
|
}
|
|
1189
1334
|
module.exports = DymoAPI;
|