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