@xuda.io/notification_module 1.1.128 → 1.1.129
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/index.mjs +98 -1
- package/index_ms.mjs +4 -0
- package/index_msa.mjs +4 -0
- package/package.json +3 -2
package/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import admin from 'firebase-admin';
|
|
4
|
+
import { parsePhoneNumberFromString } from 'libphonenumber-js';
|
|
4
5
|
|
|
5
6
|
console.log('Notifications Module loaded...');
|
|
6
7
|
|
|
@@ -23,6 +24,78 @@ admin.initializeApp({
|
|
|
23
24
|
const messaging = admin.messaging();
|
|
24
25
|
//////////////////////////////////////////////
|
|
25
26
|
|
|
27
|
+
//////////////////// SMS (Twilio) /////////////////////
|
|
28
|
+
// Best-effort HTML->plain for SMS bodies (templates are authored as HTML).
|
|
29
|
+
const _strip_html = (s) =>
|
|
30
|
+
String(s || '')
|
|
31
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
32
|
+
.replace(/<[^>]+>/g, ' ')
|
|
33
|
+
.replace(/ /gi, ' ')
|
|
34
|
+
.replace(/&/gi, '&')
|
|
35
|
+
.replace(/\s+/g, ' ')
|
|
36
|
+
.trim();
|
|
37
|
+
|
|
38
|
+
// Normalize a stored phone (+ its account country) to E.164 for Twilio.
|
|
39
|
+
const _to_e164 = (phone, country) => {
|
|
40
|
+
if (!phone) return null;
|
|
41
|
+
const raw = String(phone).trim();
|
|
42
|
+
try {
|
|
43
|
+
const pn = parsePhoneNumberFromString(raw, (country || '').toUpperCase() || undefined);
|
|
44
|
+
if (pn && pn.isValid()) return pn.number; // E.164
|
|
45
|
+
} catch (e) { /* fall through */ }
|
|
46
|
+
const digits = raw.replace(/[^\d+]/g, '');
|
|
47
|
+
if (/^\+\d{7,15}$/.test(digits)) return digits;
|
|
48
|
+
if (/^00\d{7,15}$/.test(digits)) return '+' + digits.slice(2);
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Send an SMS via Twilio (Messaging Service). Returns
|
|
53
|
+
// { sent, log_only?, sid?, status?, to?, error? }.
|
|
54
|
+
// GATED: unless _conf.bounce_monitor.sms_enabled === true (or opts.force) AND the
|
|
55
|
+
// twilio creds + a sender (messaging_service_sid|from) exist, it composes + LOGS
|
|
56
|
+
// only and sends nothing — so we can build/test before toll-free verification is
|
|
57
|
+
// approved. Never logs credential values.
|
|
58
|
+
export const send_sms = async (to, body, opts = {}) => {
|
|
59
|
+
const tw = _conf.twilio || {};
|
|
60
|
+
const enabled = _conf.bounce_monitor?.sms_enabled === true || opts.force === true;
|
|
61
|
+
const to_e164 = _to_e164(to, opts.country);
|
|
62
|
+
const text = _strip_html(body).slice(0, 480);
|
|
63
|
+
if (!to_e164) {
|
|
64
|
+
console.warn(`[sms] skip: cannot normalize "${to}" (country=${opts.country || '?'})`);
|
|
65
|
+
return { sent: false, error: 'invalid_phone' };
|
|
66
|
+
}
|
|
67
|
+
const have_creds = tw.account_sid && tw.api_key_sid && tw.api_key_secret && (tw.messaging_service_sid || tw.from);
|
|
68
|
+
if (!enabled || !have_creds) {
|
|
69
|
+
console.log(`[sms log_only]${enabled ? '' : ' (disabled)'}${have_creds ? '' : ' (no creds)'} -> ${to_e164} :: ${text}`);
|
|
70
|
+
return { sent: false, log_only: true, to: to_e164 };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const url = `https://api.twilio.com/2010-04-01/Accounts/${tw.account_sid}/Messages.json`;
|
|
74
|
+
const form = new URLSearchParams();
|
|
75
|
+
form.set('To', to_e164);
|
|
76
|
+
if (tw.messaging_service_sid) form.set('MessagingServiceSid', tw.messaging_service_sid);
|
|
77
|
+
else form.set('From', tw.from);
|
|
78
|
+
form.set('Body', text);
|
|
79
|
+
const auth = Buffer.from(`${tw.api_key_sid}:${tw.api_key_secret}`).toString('base64');
|
|
80
|
+
const resp = await fetch(url, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { Authorization: `Basic ${auth}`, 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
83
|
+
body: form.toString(),
|
|
84
|
+
});
|
|
85
|
+
const data = await resp.json().catch(() => ({}));
|
|
86
|
+
if (!resp.ok) {
|
|
87
|
+
console.error(`[sms] Twilio ${resp.status} (code ${data?.code || '?'}):`, data?.message || data);
|
|
88
|
+
return { sent: false, error: data?.message || `http_${resp.status}`, code: data?.code, to: to_e164 };
|
|
89
|
+
}
|
|
90
|
+
console.log(`[sms] sent sid=${data.sid} to=${to_e164} status=${data.status}`);
|
|
91
|
+
return { sent: true, sid: data.sid, status: data.status, to: to_e164 };
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.error('[sms] send error:', err.message);
|
|
94
|
+
return { sent: false, error: err.message, to: to_e164 };
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
//////////////////////////////////////////////
|
|
98
|
+
|
|
26
99
|
export const get_new_notification_count = async (req) => {
|
|
27
100
|
return await db_module.get_couch_view('xuda_notification', 'new_notifications_count', {
|
|
28
101
|
key: [req.uid],
|
|
@@ -61,7 +134,7 @@ export const update_notification_status = async (req) => {
|
|
|
61
134
|
//, _ch= global[`_notification_module_ch`]
|
|
62
135
|
export const submit_notification = async (req) => {
|
|
63
136
|
try {
|
|
64
|
-
let { type, app_id, to_app_id, uid_arr, subject = '', body = '', delivery_method = [], display_type = 'info', ref, email, sender_uid, system, topic, params } = req;
|
|
137
|
+
let { type, app_id, to_app_id, uid_arr, subject = '', body = '', sms_body = '', delivery_method = [], display_type = 'info', ref, email, sender_uid, system, topic, params } = req;
|
|
65
138
|
let obj = {};
|
|
66
139
|
const { notification_categories } = await import(path.join(process.env.XUDA_HOME, 'notification_templates.mjs'));
|
|
67
140
|
|
|
@@ -89,6 +162,9 @@ export const submit_notification = async (req) => {
|
|
|
89
162
|
if (!body) {
|
|
90
163
|
body = _.template(_topic.body)(params);
|
|
91
164
|
}
|
|
165
|
+
if (!sms_body && _topic.sms_body) {
|
|
166
|
+
sms_body = _.template(_topic.sms_body)(params);
|
|
167
|
+
}
|
|
92
168
|
} else {
|
|
93
169
|
return {
|
|
94
170
|
code: -1,
|
|
@@ -237,6 +313,27 @@ export const submit_notification = async (req) => {
|
|
|
237
313
|
}
|
|
238
314
|
break;
|
|
239
315
|
|
|
316
|
+
case 'sms': {
|
|
317
|
+
if (!account_ret) {
|
|
318
|
+
account_ret = await db_module.get_couch_doc('xuda_accounts', uid).catch(() => null);
|
|
319
|
+
}
|
|
320
|
+
const ai = account_ret?.data?.account_info || {};
|
|
321
|
+
const phone = req.phone || ai.phone_number;
|
|
322
|
+
const country = req.country || ai.country;
|
|
323
|
+
const text = sms_body || _strip_html(body);
|
|
324
|
+
if (!phone) {
|
|
325
|
+
console.warn(`[sms] no phone on ${uid}; sms channel skipped`);
|
|
326
|
+
send_result[uid] = false;
|
|
327
|
+
obj.sms_status = 'no_phone';
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
const r = await send_sms(phone, text, { country });
|
|
331
|
+
obj.sms_to = r.to;
|
|
332
|
+
obj.sms_status = r.sent ? (r.sid || 'sent') : r.log_only ? 'log_only' : r.error || 'failed';
|
|
333
|
+
if (!r.sent && !r.log_only) send_result[uid] = false;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
|
|
240
337
|
default:
|
|
241
338
|
send_result[uid] = false;
|
|
242
339
|
break;
|
package/index_ms.mjs
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
export const send_sms = async function (...args) {
|
|
21
|
+
return await broker.send_to_queue("send_sms", ...args);
|
|
22
|
+
};
|
|
23
|
+
|
|
20
24
|
export const get_new_notification_count = async function (...args) {
|
|
21
25
|
return await broker.send_to_queue("get_new_notification_count", ...args);
|
|
22
26
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
export const send_sms = function (...args) {
|
|
21
|
+
broker.send_to_queue_async("send_sms", ...args);
|
|
22
|
+
};
|
|
23
|
+
|
|
20
24
|
export const get_new_notification_count = function (...args) {
|
|
21
25
|
broker.send_to_queue_async("get_new_notification_count", ...args);
|
|
22
26
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xuda.io/notification_module",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.129",
|
|
4
4
|
"description": "Xuda Notification Server Module",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"lodash": "^4.17.21",
|
|
8
8
|
"amqplib": "^0.10.9",
|
|
9
|
-
"firebase-admin": "^12.0.0"
|
|
9
|
+
"firebase-admin": "^12.0.0",
|
|
10
|
+
"libphonenumber-js": "^1.11.4"
|
|
10
11
|
},
|
|
11
12
|
"devDependencies": {},
|
|
12
13
|
"scripts": {
|