@xeplr/utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/index.js +46 -0
- package/isomorphic/codes.js +51 -0
- package/isomorphic/countries.js +200 -0
- package/isomorphic/countries.json +786 -0
- package/isomorphic/crypto.js +123 -0
- package/isomorphic/emailNormalizer.js +56 -0
- package/isomorphic/helpers.js +52 -0
- package/isomorphic/index.js +93 -0
- package/isomorphic/messages.js +36 -0
- package/isomorphic/responseReader.js +57 -0
- package/isomorphic/states.js +44 -0
- package/isomorphic/states.json +296 -0
- package/lib/cache.js +166 -0
- package/lib/email.js +303 -0
- package/lib/fileUploader.js +135 -0
- package/lib/helpers.js +18 -0
- package/lib/logger.js +51 -0
- package/lib/queue.js +160 -0
- package/lib/rateLimiter.js +100 -0
- package/lib/response.js +86 -0
- package/lib/sms.js +278 -0
- package/package.json +40 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
var MemoryStore = {
|
|
2
|
+
create: function(windowInSeconds) {
|
|
3
|
+
var hits = {};
|
|
4
|
+
|
|
5
|
+
// cleanup expired keys periodically
|
|
6
|
+
var cleanup = setInterval(function() {
|
|
7
|
+
var now = Date.now();
|
|
8
|
+
for (var key in hits) {
|
|
9
|
+
if (hits[key].expiresAt < now) {
|
|
10
|
+
delete hits[key];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}, windowInSeconds * 1000);
|
|
14
|
+
|
|
15
|
+
if (cleanup && cleanup.unref) cleanup.unref();
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
increment: async function(key) {
|
|
19
|
+
var now = Date.now();
|
|
20
|
+
var entry = hits[key];
|
|
21
|
+
if (!entry || entry.expiresAt < now) {
|
|
22
|
+
hits[key] = { count: 1, expiresAt: now + windowInSeconds * 1000 };
|
|
23
|
+
return { count: 1, remaining: windowInSeconds };
|
|
24
|
+
}
|
|
25
|
+
entry.count++;
|
|
26
|
+
var remaining = Math.ceil((entry.expiresAt - now) / 1000);
|
|
27
|
+
return { count: entry.count, remaining: remaining };
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
var RedisStore = {
|
|
34
|
+
create: function(windowInSeconds) {
|
|
35
|
+
var cache = require('./cache');
|
|
36
|
+
var client = cache.getClient();
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
increment: async function(key) {
|
|
40
|
+
var redisKey = 'xeplr:ratelimit:' + key;
|
|
41
|
+
var count = await client.incr(redisKey);
|
|
42
|
+
if (count === 1) {
|
|
43
|
+
await client.expire(redisKey, windowInSeconds);
|
|
44
|
+
}
|
|
45
|
+
var ttl = await client.ttl(redisKey);
|
|
46
|
+
return { count: count, remaining: ttl > 0 ? ttl : windowInSeconds };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
class RateLimiter {
|
|
53
|
+
/**
|
|
54
|
+
* @param {object} options
|
|
55
|
+
* @param {number} [options.windowInSeconds=60] - Time window in seconds
|
|
56
|
+
* @param {number} [options.max=100] - Max requests per window
|
|
57
|
+
* @param {string} [options.store='memory'] - 'memory' or 'redis'
|
|
58
|
+
* @param {function} [options.keyFn] - Function to extract key from req (default: req.ip)
|
|
59
|
+
* @param {string} [options.message] - Custom error message
|
|
60
|
+
*/
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
this._windowInSeconds = options.windowInSeconds || 60;
|
|
63
|
+
this._max = options.max || 100;
|
|
64
|
+
this._keyFn = options.keyFn || function(req) { return req.ip; };
|
|
65
|
+
this._message = options.message || 'Too many requests, please try again later';
|
|
66
|
+
|
|
67
|
+
var storeType = options.store || 'memory';
|
|
68
|
+
if (storeType === 'redis') {
|
|
69
|
+
this._store = RedisStore.create(this._windowInSeconds);
|
|
70
|
+
} else {
|
|
71
|
+
this._store = MemoryStore.create(this._windowInSeconds);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
middleware() {
|
|
76
|
+
var self = this;
|
|
77
|
+
return async function(req, res, next) {
|
|
78
|
+
try {
|
|
79
|
+
var key = self._keyFn(req);
|
|
80
|
+
var result = await self._store.increment(key);
|
|
81
|
+
|
|
82
|
+
res.setHeader('X-RateLimit-Limit', self._max);
|
|
83
|
+
res.setHeader('X-RateLimit-Remaining', Math.max(0, self._max - result.count));
|
|
84
|
+
res.setHeader('X-RateLimit-Reset', result.remaining);
|
|
85
|
+
|
|
86
|
+
if (result.count > self._max) {
|
|
87
|
+
res.setHeader('Retry-After', result.remaining);
|
|
88
|
+
return res.status(429).json({ error: self._message });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
next();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// if rate limiter fails, let the request through
|
|
94
|
+
next();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = RateLimiter;
|
package/lib/response.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response helper — sends a fixed-format JSON response.
|
|
3
|
+
*
|
|
4
|
+
* Format:
|
|
5
|
+
* {
|
|
6
|
+
* code: 'SUC_LOGIN' | 'ERR_INVALID_FILE' | STATUS.SUCCESS | ...,
|
|
7
|
+
* message: 'Human-readable message (i18n aware)',
|
|
8
|
+
* error: null | { name, message, ...details },
|
|
9
|
+
* dataArray: [],
|
|
10
|
+
* updatedIds: []
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const { respond } = require('@xeplr/utils');
|
|
15
|
+
* const { HTTP, STATUS } = require('@xeplr/utils/isomorphic');
|
|
16
|
+
*
|
|
17
|
+
* // Success
|
|
18
|
+
* respond(res, HTTP.OK, STATUS.SUCCESS, 'success', { dataArray: orders });
|
|
19
|
+
*
|
|
20
|
+
* // Error
|
|
21
|
+
* respond(res, HTTP.BAD_REQUEST, STATUS.BAD_REQUEST, 'bad_request', { error: err });
|
|
22
|
+
*
|
|
23
|
+
* // With updatedIds
|
|
24
|
+
* respond(res, HTTP.OK, STATUS.UPDATED, 'updated', { updatedIds: [1, 2, 3] });
|
|
25
|
+
*
|
|
26
|
+
* // Minimal
|
|
27
|
+
* respond(res, HTTP.OK, STATUS.SUCCESS, 'success');
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const { msg } = require('../isomorphic');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Sanitize an error object for serialization (strips stack trace).
|
|
34
|
+
*/
|
|
35
|
+
function sanitizeError(err) {
|
|
36
|
+
if (!err) return null;
|
|
37
|
+
|
|
38
|
+
const clean = {
|
|
39
|
+
name: err.name || 'Error',
|
|
40
|
+
message: err.message || String(err),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Preserve any extra properties (e.g. err.code, err.field, err.details)
|
|
44
|
+
// but skip stack, __proto__, constructor
|
|
45
|
+
const skip = new Set(['name', 'message', 'stack']);
|
|
46
|
+
for (const key of Object.keys(err)) {
|
|
47
|
+
if (!skip.has(key)) {
|
|
48
|
+
clean[key] = err[key];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return clean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Send a standardized response.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} res - Express response object
|
|
59
|
+
* @param {number} httpCode - HTTP status code (from HTTP constants)
|
|
60
|
+
* @param {string} statusCode - Application status code (from STATUS constants)
|
|
61
|
+
* @param {string} messageKey - Key in MESSAGES for i18n lookup
|
|
62
|
+
* @param {object} [options] - Optional overrides
|
|
63
|
+
* @param {Array} [options.dataArray] - Response data (default: [])
|
|
64
|
+
* @param {Array} [options.updatedIds] - IDs affected by the operation (default: [])
|
|
65
|
+
* @param {Error} [options.error] - Error object, sanitized before sending (default: null)
|
|
66
|
+
* @param {string} [options.message] - Override the i18n message with a custom string
|
|
67
|
+
* @param {object} [options.pagination] - { page, limit, total, totalPages }
|
|
68
|
+
*/
|
|
69
|
+
function respond(res, httpCode, statusCode, messageKey, options = {}) {
|
|
70
|
+
const body = {
|
|
71
|
+
code: statusCode,
|
|
72
|
+
message: options.message || msg(messageKey),
|
|
73
|
+
error: sanitizeError(options.error),
|
|
74
|
+
dataArray: options.dataArray || [],
|
|
75
|
+
updatedIds: options.updatedIds || [],
|
|
76
|
+
sessionId: res.req && res.req.sessionId || null,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (options.pagination) {
|
|
80
|
+
body.pagination = options.pagination;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return res.status(httpCode).send(body);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { respond, sanitizeError };
|
package/lib/sms.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMS Service — generic wrapper for any SMS provider.
|
|
3
|
+
*
|
|
4
|
+
* Each provider is an HTTP API definition with multiple actions.
|
|
5
|
+
* Register providers once, then send via provider + action name.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
*
|
|
9
|
+
* var sms = require('@xeplr/utils').sms;
|
|
10
|
+
*
|
|
11
|
+
* // Simple provider — single action (default)
|
|
12
|
+
* sms.register('twilio', {
|
|
13
|
+
* actions: {
|
|
14
|
+
* send: {
|
|
15
|
+
* url: 'https://api.twilio.com/2010-04-01/Accounts/{{accountSid}}/Messages.json',
|
|
16
|
+
* method: 'POST',
|
|
17
|
+
* auth: { user: '{{accountSid}}', password: '{{authToken}}' },
|
|
18
|
+
* contentType: 'form',
|
|
19
|
+
* body: { From: '{{from}}', To: '{{to}}', Body: '{{message}}' }
|
|
20
|
+
* }
|
|
21
|
+
* },
|
|
22
|
+
* params: { accountSid: 'AC...', authToken: '...', from: '+1234567890' }
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Provider with multiple actions + chained auth
|
|
26
|
+
* sms.register('kaleyra', {
|
|
27
|
+
* actions: {
|
|
28
|
+
* auth: {
|
|
29
|
+
* url: 'https://api.kaleyra.io/v1/{{sid}}/auth/token',
|
|
30
|
+
* method: 'POST',
|
|
31
|
+
* contentType: 'json',
|
|
32
|
+
* body: { api_key: '{{apiKey}}' },
|
|
33
|
+
* extract: { token: 'data.token' } // extract token from response into params
|
|
34
|
+
* },
|
|
35
|
+
* otp: {
|
|
36
|
+
* url: 'https://api.kaleyra.io/v1/{{sid}}/messages',
|
|
37
|
+
* method: 'POST',
|
|
38
|
+
* headers: { Authorization: 'Bearer {{token}}' },
|
|
39
|
+
* contentType: 'json',
|
|
40
|
+
* body: { to: '{{to}}', type: 'OTP', body: '{{message}}' },
|
|
41
|
+
* before: ['auth'] // run auth action first, merge extracted params
|
|
42
|
+
* },
|
|
43
|
+
* promo: {
|
|
44
|
+
* url: 'https://api.kaleyra.io/v1/{{sid}}/messages',
|
|
45
|
+
* method: 'POST',
|
|
46
|
+
* headers: { Authorization: 'Bearer {{token}}' },
|
|
47
|
+
* contentType: 'json',
|
|
48
|
+
* body: { to: '{{to}}', type: 'PROMO', body: '{{message}}' },
|
|
49
|
+
* before: ['auth']
|
|
50
|
+
* }
|
|
51
|
+
* },
|
|
52
|
+
* params: { sid: '...', apiKey: '...' }
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // Send
|
|
56
|
+
* await sms.send('twilio', { to: '+91...', message: 'Hello!' });
|
|
57
|
+
* await sms.send('kaleyra', 'otp', { to: '+91...', message: 'OTP: 1234' });
|
|
58
|
+
* await sms.send('kaleyra', 'promo', { to: '+91...', message: 'Sale!' });
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
var _providers = {};
|
|
62
|
+
var _defaultProvider = null;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register an SMS provider.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} name - Provider name
|
|
68
|
+
* @param {object} config
|
|
69
|
+
* @param {object} config.actions - Named actions { actionName: { url, method, headers, auth, contentType, body, before, extract } }
|
|
70
|
+
* @param {object} [config.params] - Default params shared across all actions
|
|
71
|
+
* @param {function} [config.parseResponse] - Custom response parser for all actions
|
|
72
|
+
*/
|
|
73
|
+
function register(name, config) {
|
|
74
|
+
_providers[name] = config;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Set the default provider.
|
|
79
|
+
*/
|
|
80
|
+
function setDefault(name) {
|
|
81
|
+
_defaultProvider = name;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function _resolvePlaceholders(str, params) {
|
|
85
|
+
if (typeof str !== 'string') return str;
|
|
86
|
+
return str.replace(/\{\{(\w+)\}\}/g, function(match, key) {
|
|
87
|
+
return params[key] !== undefined ? String(params[key]) : match;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function _resolveObject(obj, params) {
|
|
92
|
+
if (typeof obj === 'string') return _resolvePlaceholders(obj, params);
|
|
93
|
+
if (Array.isArray(obj)) return obj.map(function(v) { return _resolveObject(v, params); });
|
|
94
|
+
if (obj && typeof obj === 'object') {
|
|
95
|
+
var resolved = {};
|
|
96
|
+
var keys = Object.keys(obj);
|
|
97
|
+
for (var i = 0; i < keys.length; i++) {
|
|
98
|
+
resolved[keys[i]] = _resolveObject(obj[keys[i]], params);
|
|
99
|
+
}
|
|
100
|
+
return resolved;
|
|
101
|
+
}
|
|
102
|
+
return obj;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Extract a nested value from an object using dot notation.
|
|
107
|
+
* e.g. _extractPath({ data: { token: 'abc' } }, 'data.token') => 'abc'
|
|
108
|
+
*/
|
|
109
|
+
function _extractPath(obj, path) {
|
|
110
|
+
var parts = path.split('.');
|
|
111
|
+
var val = obj;
|
|
112
|
+
for (var i = 0; i < parts.length; i++) {
|
|
113
|
+
if (val == null) return undefined;
|
|
114
|
+
val = val[parts[i]];
|
|
115
|
+
}
|
|
116
|
+
return val;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Execute a single action against a provider.
|
|
121
|
+
*/
|
|
122
|
+
async function _executeAction(action, params) {
|
|
123
|
+
var url = _resolvePlaceholders(action.url, params);
|
|
124
|
+
var method = (action.method || 'POST').toUpperCase();
|
|
125
|
+
|
|
126
|
+
var headers = {};
|
|
127
|
+
if (action.headers) {
|
|
128
|
+
headers = _resolveObject(action.headers, params);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var fetchOptions = { method: method, headers: {} };
|
|
132
|
+
|
|
133
|
+
// Basic auth
|
|
134
|
+
if (action.auth) {
|
|
135
|
+
var user = _resolvePlaceholders(action.auth.user, params);
|
|
136
|
+
var password = _resolvePlaceholders(action.auth.password, params);
|
|
137
|
+
fetchOptions.headers['Authorization'] = 'Basic ' + Buffer.from(user + ':' + password).toString('base64');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Object.assign(fetchOptions.headers, headers);
|
|
141
|
+
|
|
142
|
+
// Body
|
|
143
|
+
if (action.body) {
|
|
144
|
+
var body = _resolveObject(action.body, params);
|
|
145
|
+
var contentType = action.contentType || 'json';
|
|
146
|
+
|
|
147
|
+
if (contentType === 'form') {
|
|
148
|
+
fetchOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
149
|
+
var parts = [];
|
|
150
|
+
var bodyKeys = Object.keys(body);
|
|
151
|
+
for (var i = 0; i < bodyKeys.length; i++) {
|
|
152
|
+
parts.push(encodeURIComponent(bodyKeys[i]) + '=' + encodeURIComponent(body[bodyKeys[i]]));
|
|
153
|
+
}
|
|
154
|
+
fetchOptions.body = parts.join('&');
|
|
155
|
+
} else {
|
|
156
|
+
fetchOptions.headers['Content-Type'] = 'application/json';
|
|
157
|
+
fetchOptions.body = JSON.stringify(body);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
var res = await fetch(url, fetchOptions);
|
|
162
|
+
var raw;
|
|
163
|
+
try {
|
|
164
|
+
raw = await res.json();
|
|
165
|
+
} catch (e) {
|
|
166
|
+
raw = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { ok: res.ok, status: res.status, raw: raw };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Send an SMS.
|
|
174
|
+
*
|
|
175
|
+
* Signatures:
|
|
176
|
+
* send('twilio', { to, message }) — uses 'send' action
|
|
177
|
+
* send('kaleyra', 'otp', { to, message }) — uses named action
|
|
178
|
+
* send({ to, message }) — uses default provider + 'send' action
|
|
179
|
+
*
|
|
180
|
+
* @returns {Promise<{ success, messageId?, raw?, error? }>}
|
|
181
|
+
*/
|
|
182
|
+
async function send(providerName, actionOrParams, sendParams) {
|
|
183
|
+
// Resolve arguments
|
|
184
|
+
var actionName, params;
|
|
185
|
+
|
|
186
|
+
if (typeof providerName === 'object') {
|
|
187
|
+
// send({ to, message }) — default provider, 'send' action
|
|
188
|
+
params = providerName;
|
|
189
|
+
providerName = _defaultProvider;
|
|
190
|
+
actionName = 'send';
|
|
191
|
+
} else if (typeof actionOrParams === 'string') {
|
|
192
|
+
// send('provider', 'action', { to, message })
|
|
193
|
+
actionName = actionOrParams;
|
|
194
|
+
params = sendParams || {};
|
|
195
|
+
} else {
|
|
196
|
+
// send('provider', { to, message }) — 'send' action
|
|
197
|
+
actionName = 'send';
|
|
198
|
+
params = actionOrParams || {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!providerName) {
|
|
202
|
+
throw new Error('SMS provider name required. Call sms.setDefault() or pass provider name.');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
var config = _providers[providerName];
|
|
206
|
+
if (!config) {
|
|
207
|
+
throw new Error('SMS provider "' + providerName + '" not registered.');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
var action = config.actions[actionName];
|
|
211
|
+
if (!action) {
|
|
212
|
+
throw new Error('Action "' + actionName + '" not found on provider "' + providerName + '".');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Merge default params with send-time params
|
|
216
|
+
var mergedParams = Object.assign({}, config.params || {}, params);
|
|
217
|
+
|
|
218
|
+
// Run prerequisite actions (e.g. auth)
|
|
219
|
+
if (action.before && action.before.length) {
|
|
220
|
+
for (var i = 0; i < action.before.length; i++) {
|
|
221
|
+
var beforeName = action.before[i];
|
|
222
|
+
var beforeAction = config.actions[beforeName];
|
|
223
|
+
if (!beforeAction) {
|
|
224
|
+
throw new Error('Before-action "' + beforeName + '" not found on provider "' + providerName + '".');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var beforeResult = await _executeAction(beforeAction, mergedParams);
|
|
228
|
+
if (!beforeResult.ok) {
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
error: 'Pre-action "' + beforeName + '" failed with status ' + beforeResult.status,
|
|
232
|
+
raw: beforeResult.raw
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Extract values from response into params
|
|
237
|
+
if (beforeAction.extract && beforeResult.raw) {
|
|
238
|
+
var extractKeys = Object.keys(beforeAction.extract);
|
|
239
|
+
for (var j = 0; j < extractKeys.length; j++) {
|
|
240
|
+
var paramKey = extractKeys[j];
|
|
241
|
+
var responsePath = beforeAction.extract[paramKey];
|
|
242
|
+
mergedParams[paramKey] = _extractPath(beforeResult.raw, responsePath);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Execute main action
|
|
249
|
+
var result = await _executeAction(action, mergedParams);
|
|
250
|
+
|
|
251
|
+
// Custom response parser
|
|
252
|
+
if (config.parseResponse) {
|
|
253
|
+
return config.parseResponse(result.raw, result);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!result.ok) {
|
|
257
|
+
return {
|
|
258
|
+
success: false,
|
|
259
|
+
error: (result.raw && (result.raw.message || result.raw.error || result.raw.error_message)) || 'SMS failed with status ' + result.status,
|
|
260
|
+
raw: result.raw
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
messageId: result.raw && (result.raw.sid || result.raw.messageId || result.raw.message_id || result.raw.id) || null,
|
|
267
|
+
raw: result.raw
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get list of registered provider names.
|
|
273
|
+
*/
|
|
274
|
+
function getProviders() {
|
|
275
|
+
return Object.keys(_providers);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = { register, setDefault, send, getProviders };
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xeplr/utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Utility functions: email (SMTP, AWS SES, Azure, Brevo), cache, queue, logging client, and helpers",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"files": ["index.js", "lib/", "isomorphic/"],
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./isomorphic": "./isomorphic/index.js",
|
|
10
|
+
"./isomorphic/crypto": "./isomorphic/crypto.js",
|
|
11
|
+
"./isomorphic/countries": "./isomorphic/countries.js",
|
|
12
|
+
"./isomorphic/states": "./isomorphic/states.js",
|
|
13
|
+
"./lib/cache": "./lib/cache.js",
|
|
14
|
+
"./lib/email": "./lib/email.js",
|
|
15
|
+
"./lib/fileUploader": "./lib/fileUploader.js",
|
|
16
|
+
"./lib/helpers": "./lib/helpers.js",
|
|
17
|
+
"./lib/logger": "./lib/logger.js",
|
|
18
|
+
"./lib/queue": "./lib/queue.js",
|
|
19
|
+
"./lib/rateLimiter": "./lib/rateLimiter.js",
|
|
20
|
+
"./lib/response": "./lib/response.js"
|
|
21
|
+
},
|
|
22
|
+
"keywords": ["email", "brevo", "ses", "smtp", "cache", "redis", "queue", "logger", "utilities"],
|
|
23
|
+
"author": "xeplr",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": { "type": "git", "url": "https://github.com/Xeplr/xeplr-utils" },
|
|
26
|
+
"publishConfig": { "access": "public" },
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"ioredis": "^5.6.1",
|
|
29
|
+
"multer": "^1.4.5-lts.1",
|
|
30
|
+
"nodemailer": "^8.0.2"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@aws-sdk/client-ses": "^3.0.0",
|
|
34
|
+
"@azure/communication-email": "^1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"@aws-sdk/client-ses": { "optional": true },
|
|
38
|
+
"@azure/communication-email": { "optional": true }
|
|
39
|
+
}
|
|
40
|
+
}
|