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