dymo-api 1.2.37 → 1.2.39

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