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