@nka212bg/backend-utils 0.1.51 → 0.1.53

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.
@@ -0,0 +1,26 @@
1
+ <!-- wrapper: fills the available width, up to 400px -->
2
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="width: 100%; max-width: 500px">
3
+ <tr>
4
+ <td>
5
+ <!-- divider: mid gray at 70% (solid fallback for clients without rgba), dodger blue accent at the end -->
6
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation">
7
+ <tr>
8
+ <td style="border-top: 1px solid #cfcfcf; border-top: 1px solid rgba(128, 128, 128, 0.3); font-size: 0; line-height: 0">&nbsp;</td>
9
+ <td width="24" style="border-top: 1px solid #1e90ff; font-size: 0; line-height: 0">&nbsp;</td>
10
+ </tr>
11
+ </table>
12
+ <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="font-family: system-ui, sans-serif; font-size: 12px">
13
+ <tr>
14
+ <!--logo-->
15
+ <td width="30" style="padding: 12px 10px 0 0; vertical-align: middle">
16
+ <a href="{{ baseUrl }}" target="_blank"><img src="{{ logoUrl }}" alt="ℹ️" height="20" style="display: block; height: 20px; border: 0" /></a>
17
+ </td>
18
+ <!--/logo-->
19
+ <td style="padding-top: 12px; vertical-align: middle">
20
+ <a href="{{ baseUrl }}" target="_blank" style="text-decoration: none; font-size: 14px">{{ year }} {{{ name }}}</a>
21
+ </td>
22
+ </tr>
23
+ </table>
24
+ </td>
25
+ </tr>
26
+ </table>
@@ -3,7 +3,7 @@ const { strToEmailsArray, handlebars } = require("../misc");
3
3
  const { location } = require("../location");
4
4
 
5
5
  const nodemailer = require("nodemailer");
6
- const defaultTemplate = readFileSync(`${__dirname}/defaultTemplate.html`, "utf8");
6
+ const defaultFooter = readFileSync(`${__dirname}/defaultFooter.html`, "utf8");
7
7
 
8
8
  !existsSync(`${__basedir}/state`) && mkdirSync(`${__basedir}/state`, { recursive: true });
9
9
  const emailListFilePath = `${__basedir}/state/useremailList.json`;
@@ -128,10 +128,19 @@ const createInstance = (options = {}) => {
128
128
  const { error: locationError, data: locationData } = location({ ip: userIp });
129
129
 
130
130
  if (!locationError) {
131
- userlocation += `<br>Location: ${locationData.city} ${locationData.country_name} <a href="https://www.google.com/maps/@${locationData.coordinates.latitude},${locationData.coordinates.longitude},15z" target="_blank">see in map</a>`;
131
+ userlocation += `<br />Location: ${locationData.city} ${locationData.country_name} <a href="https://www.google.com/maps/@${locationData.coordinates.latitude},${locationData.coordinates.longitude},15z" target="_blank">see in map</a>`;
132
132
  }
133
133
 
134
- content += `<br/><br/>${userlocation}<br/><br/>`;
134
+ content += `<br /><br />${userlocation}<br /><br />`;
135
+ }
136
+
137
+ if (baseUrl) {
138
+ content += handlebars(logoUrl ? defaultFooter : defaultFooter.replace(/<!--logo-->[\s\S]*?<!--\/logo-->/, ""), {
139
+ baseUrl,
140
+ logoUrl,
141
+ name: name || "",
142
+ year: new Date().getFullYear(),
143
+ });
135
144
  }
136
145
 
137
146
  return new Promise((resolve, reject) => {
@@ -147,37 +156,9 @@ const createInstance = (options = {}) => {
147
156
  return;
148
157
  }
149
158
 
150
- const html = `
151
- <div style="padding: 20px">
152
- <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="margin: 0; max-width: 500px; padding: 0; font-family: system-ui; font-size: 14px; background-color: transparent">
153
- <tbody>
154
- ${
155
- !logoUrl
156
- ? ""
157
- : `<tr>
158
- <td style="padding-bottom: 6px">
159
- <img src="${logoUrl}" style="height: 36px; background-size: contain; background-repeat: no-repeat" />
160
- </td>
161
- </tr>`
162
- }
163
- <tr><td style="padding-bottom: 20px">${content}</td></tr>
164
- ${
165
- !baseUrl
166
- ? ""
167
- : `<tr>
168
- <td>
169
- <a href="${baseUrl}" target="_blank" style="text-decoration: none; color: gray; font-size: 12px">${new Date().getFullYear()} ${name || ""}</a>
170
- </td>
171
- </tr>`
172
- }
173
- </tbody>
174
- </table>
175
- </div>`;
176
-
177
159
  const mailOptions = {
178
160
  from: name ? { name, address: smtpUser } : smtpUser,
179
- // html: handlebars({ template: defaultTemplate, content, subject, logoUrl: `${process.env.BASE_URL}/logo_black.png` }),
180
- html,
161
+ html: content,
181
162
  subject,
182
163
  to: e,
183
164
  attachments,
package/misc.js CHANGED
@@ -118,17 +118,14 @@ exports.createToken = ({ length = 14, type } = {}) => {
118
118
  }
119
119
  };
120
120
 
121
- exports.handlebars = ({ template, ...params } = {}) => {
122
- const placeholders = template.match(/(\{\{)(.*?)(\}\})/g);
121
+ exports.escapeHtml = (s) => String(s).replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
123
122
 
124
- for (const p in params) {
125
- const placeholder = placeholders.find((e) => e.includes(p));
126
- if (!placeholder) continue;
127
-
128
- template = template.replace(RegExp(placeholder, "g"), params[p]);
129
- }
130
-
131
- return template;
123
+ // {{ key }} → raw (trusted), {{{ key }}} → HTML-escaped (untrusted); supports nested keys like {{ user.name }}
124
+ exports.handlebars = (template = "", params = {}) => {
125
+ return template.replace(/\{\{(\{?)\s*([\w.]+)\s*\}?\}\}/g, (match, safe, key) => {
126
+ const value = key.split(".").reduce((o, k) => o?.[k], params);
127
+ return value == null ? match : safe ? this.escapeHtml(value) : value;
128
+ });
132
129
  };
133
130
 
134
131
  exports.shortCount = (value, bites) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nka212bg/backend-utils",
3
3
  "author": "nka212bg",
4
- "version": "0.1.51",
4
+ "version": "0.1.53",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
7
  "@maxmind/geoip2-node": "^5.0.0",
@@ -1,21 +0,0 @@
1
- <div style="padding: 20px">
2
- <table border="0" cellpadding="0" cellspacing="0" width="100%" role="presentation" style="margin: 0; max-width: 500px; padding: 0; font-family: system-ui; font-size: 14px; background-color: transparent">
3
- <tbody>
4
- <tr>
5
- <td style="padding-bottom: 6px">
6
- <img src="{{ logoUrl }}" style="height: 36px; background-size: contain; background-repeat: no-repeat" />
7
- </td>
8
- </tr>
9
-
10
- <tr>
11
- <td style="padding-bottom: 20px">{{ content }}</td>
12
- </tr>
13
-
14
- <tr>
15
- <td>
16
- <a href="https://buukmarks.com" target="_blank" style="text-decoration: none; color: gray; font-size: 12px">2024 Buukmarks.com</a>
17
- </td>
18
- </tr>
19
- </tbody>
20
- </table>
21
- </div>