@technomoron/mail-magic-client 1.0.28 → 1.0.32

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.
@@ -1,332 +1,325 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const fs_1 = __importDefault(require("fs"));
7
- const path_1 = __importDefault(require("path"));
8
- const email_addresses_1 = __importDefault(require("email-addresses"));
9
- const nunjucks_1 = __importDefault(require("nunjucks"));
1
+ 'use strict';
2
+ var __importDefault =
3
+ (this && this.__importDefault) ||
4
+ function (mod) {
5
+ return mod && mod.__esModule ? mod : { default: mod };
6
+ };
7
+ Object.defineProperty(exports, '__esModule', { value: true });
8
+ const fs_1 = __importDefault(require('fs'));
9
+ const path_1 = __importDefault(require('path'));
10
+ const email_addresses_1 = __importDefault(require('email-addresses'));
11
+ const nunjucks_1 = __importDefault(require('nunjucks'));
10
12
  class templateClient {
11
- constructor(baseURL, apiKey) {
12
- this.baseURL = baseURL;
13
- this.apiKey = apiKey;
14
- if (!apiKey || !baseURL) {
15
- throw new Error('Apikey/api-url required');
16
- }
17
- }
18
- async request(method, command, body) {
19
- const url = `${this.baseURL}${command}`;
20
- const headers = {
21
- Accept: 'application/json',
22
- Authorization: `Bearer apikey-${this.apiKey}`
23
- };
24
- const options = {
25
- method,
26
- headers
27
- };
28
- // Avoid GET bodies (they're non-standard and can break under some proxies).
29
- if (method !== 'GET' && body !== undefined) {
30
- headers['Content-Type'] = 'application/json';
31
- options.body = JSON.stringify(body);
32
- }
33
- // console.log(JSON.stringify({ options, url }));
34
- const response = await fetch(url, options);
35
- const j = await response.json();
36
- if (response.ok) {
37
- return j;
38
- }
39
- // console.log(JSON.stringify(j, undefined, 2));
40
- if (j && j.message) {
41
- throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
42
- }
43
- else {
44
- throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
45
- }
46
- }
47
- async get(command) {
48
- return this.request('GET', command);
49
- }
50
- async post(command, body) {
51
- return this.request('POST', command, body);
52
- }
53
- async put(command, body) {
54
- return this.request('PUT', command, body);
55
- }
56
- async delete(command, body) {
57
- return this.request('DELETE', command, body);
58
- }
59
- validateEmails(list) {
60
- const valid = [], invalid = [];
61
- const emails = list
62
- .split(',')
63
- .map((email) => email.trim())
64
- .filter((email) => email !== '');
65
- emails.forEach((email) => {
66
- const parsed = email_addresses_1.default.parseOneAddress(email);
67
- if (parsed && parsed.address) {
68
- valid.push(parsed.address);
69
- }
70
- else {
71
- invalid.push(email);
72
- }
73
- });
74
- return { valid, invalid };
75
- }
76
- validateTemplate(template) {
77
- try {
78
- const env = new nunjucks_1.default.Environment(new nunjucks_1.default.FileSystemLoader(['./templates']));
79
- env.renderString(template, {});
80
- }
81
- catch (error) {
82
- if (error instanceof Error) {
83
- throw new Error(`Template validation failed: ${error.message}`);
84
- }
85
- else {
86
- throw new Error('Template validation failed with an unknown error');
87
- }
88
- }
89
- }
90
- validateSender(sender) {
91
- const exp = /^[^<>]+<[^<>]+@[^<>]+\.[^<>]+>$/;
92
- if (!exp.test(sender)) {
93
- throw new Error('Invalid sender format. Expected "Name <email@example.com>"');
94
- }
95
- }
96
- createAttachmentPayload(attachments) {
97
- const formData = new FormData();
98
- const usedFields = [];
99
- for (const attachment of attachments) {
100
- if (!attachment?.path) {
101
- throw new Error('Attachment path is required');
102
- }
103
- const raw = fs_1.default.readFileSync(attachment.path);
104
- const filename = attachment.filename || path_1.default.basename(attachment.path);
105
- const blob = new Blob([raw], attachment.contentType ? { type: attachment.contentType } : undefined);
106
- const field = attachment.field || 'attachment';
107
- formData.append(field, blob, filename);
108
- usedFields.push(field);
109
- }
110
- return { formData, usedFields };
111
- }
112
- appendFields(formData, fields) {
113
- for (const [key, value] of Object.entries(fields)) {
114
- if (value === undefined || value === null) {
115
- continue;
116
- }
117
- if (typeof value === 'string') {
118
- formData.append(key, value);
119
- }
120
- else if (typeof value === 'number' || typeof value === 'boolean') {
121
- formData.append(key, String(value));
122
- }
123
- else {
124
- formData.append(key, JSON.stringify(value));
125
- }
126
- }
127
- }
128
- async postFormData(command, formData) {
129
- const url = `${this.baseURL}${command}`;
130
- const response = await fetch(url, {
131
- method: 'POST',
132
- headers: {
133
- Authorization: `Bearer apikey-${this.apiKey}`
134
- },
135
- body: formData
136
- });
137
- const j = await response.json();
138
- if (response.ok) {
139
- return j;
140
- }
141
- if (j && j.message) {
142
- throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
143
- }
144
- throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
145
- }
146
- async storeTemplate(td) {
147
- if (!td.template) {
148
- throw new Error('No template data provided');
149
- }
150
- this.validateTemplate(td.template);
151
- if (td.sender) {
152
- this.validateSender(td.sender);
153
- }
154
- return this.storeTxTemplate(td);
155
- }
156
- async sendTemplate(std) {
157
- if (!std.name || !std.rcpt) {
158
- throw new Error('Invalid request body; name/rcpt required');
159
- }
160
- return this.sendTxMessage(std);
161
- }
162
- async storeTxTemplate(td) {
163
- if (!td.template) {
164
- throw new Error('No template data provided');
165
- }
166
- this.validateTemplate(td.template);
167
- if (td.sender) {
168
- this.validateSender(td.sender);
169
- }
170
- return this.post('/api/v1/tx/template', td);
171
- }
172
- async sendTxMessage(std) {
173
- if (!std.name || !std.rcpt) {
174
- throw new Error('Invalid request body; name/rcpt required');
175
- }
176
- const { invalid } = this.validateEmails(std.rcpt);
177
- if (invalid.length > 0) {
178
- throw new Error('Invalid email address(es): ' + invalid.join(','));
179
- }
180
- // this.validateTemplate(template);
181
- const body = {
182
- name: std.name,
183
- rcpt: std.rcpt,
184
- domain: std.domain || '',
185
- locale: std.locale || '',
186
- vars: std.vars || {},
187
- replyTo: std.replyTo,
188
- headers: std.headers
189
- };
190
- // console.log(JSON.stringify(body, undefined, 2));
191
- if (std.attachments && std.attachments.length > 0) {
192
- if (std.headers) {
193
- throw new Error('Headers are not supported with attachment uploads');
194
- }
195
- const { formData } = this.createAttachmentPayload(std.attachments);
196
- this.appendFields(formData, {
197
- name: std.name,
198
- rcpt: std.rcpt,
199
- domain: std.domain || '',
200
- locale: std.locale || '',
201
- vars: JSON.stringify(std.vars || {}),
202
- replyTo: std.replyTo
203
- });
204
- return this.postFormData('/api/v1/tx/message', formData);
205
- }
206
- return this.post('/api/v1/tx/message', body);
207
- }
208
- async storeFormTemplate(data) {
209
- if (!data.template) {
210
- throw new Error('No template data provided');
211
- }
212
- if (!data.idname) {
213
- throw new Error('Missing form identifier');
214
- }
215
- if (!data.sender) {
216
- throw new Error('Missing sender address');
217
- }
218
- if (!data.recipient) {
219
- throw new Error('Missing recipient address');
220
- }
221
- this.validateTemplate(data.template);
222
- this.validateSender(data.sender);
223
- return this.post('/api/v1/form/template', data);
224
- }
225
- async storeFormRecipient(data) {
226
- if (!data.domain) {
227
- throw new Error('Missing domain');
228
- }
229
- if (!data.idname) {
230
- throw new Error('Missing recipient identifier');
231
- }
232
- if (!data.email) {
233
- throw new Error('Missing recipient email');
234
- }
235
- const parsed = email_addresses_1.default.parseOneAddress(data.email);
236
- if (!parsed || !parsed.address) {
237
- throw new Error('Invalid recipient email address');
238
- }
239
- return this.post('/api/v1/form/recipient', data);
240
- }
241
- async sendFormMessage(data) {
242
- if (!data._mm_form_key) {
243
- throw new Error('Invalid request body; _mm_form_key required');
244
- }
245
- const fields = data.fields || {};
246
- const baseFields = {
247
- _mm_form_key: data._mm_form_key,
248
- _mm_locale: data._mm_locale,
249
- _mm_recipients: data._mm_recipients,
250
- ...fields
251
- };
252
- if (data.attachments && data.attachments.length > 0) {
253
- const normalized = data.attachments.map((attachment, idx) => {
254
- const field = attachment.field || `_mm_file${idx + 1}`;
255
- if (!field.startsWith('_mm_file')) {
256
- throw new Error('Form attachments must use multipart field names starting with _mm_file');
257
- }
258
- return { ...attachment, field };
259
- });
260
- const { formData } = this.createAttachmentPayload(normalized);
261
- this.appendFields(formData, {
262
- _mm_form_key: data._mm_form_key,
263
- _mm_locale: data._mm_locale,
264
- _mm_recipients: data._mm_recipients
265
- });
266
- this.appendFields(formData, fields);
267
- return this.postFormData('/api/v1/form/message', formData);
268
- }
269
- return this.post('/api/v1/form/message', baseFields);
270
- }
271
- async uploadAssets(data) {
272
- if (!data.domain) {
273
- throw new Error('domain is required');
274
- }
275
- if (!data.files || data.files.length === 0) {
276
- throw new Error('At least one asset file is required');
277
- }
278
- if (data.templateType && !data.template) {
279
- throw new Error('template is required when templateType is provided');
280
- }
281
- if (data.template && !data.templateType) {
282
- throw new Error('templateType is required when template is provided');
283
- }
284
- const attachments = data.files.map((input) => {
285
- if (typeof input === 'string') {
286
- return { path: input, field: 'asset' };
287
- }
288
- return { ...input, field: input.field || 'asset' };
289
- });
290
- const { formData } = this.createAttachmentPayload(attachments);
291
- this.appendFields(formData, {
292
- domain: data.domain,
293
- templateType: data.templateType,
294
- template: data.template,
295
- locale: data.locale,
296
- path: data.path
297
- });
298
- return this.postFormData('/api/v1/assets', formData);
299
- }
300
- async getSwaggerSpec() {
301
- return this.get('/api/swagger');
302
- }
303
- async fetchPublicAsset(domain, assetPath, viaApiBase = false) {
304
- if (!domain) {
305
- throw new Error('domain is required');
306
- }
307
- if (!assetPath) {
308
- throw new Error('assetPath is required');
309
- }
310
- const cleanedPath = assetPath
311
- .split('/')
312
- .filter(Boolean)
313
- .map((segment) => encodeURIComponent(segment))
314
- .join('/');
315
- if (!cleanedPath) {
316
- throw new Error('assetPath is required');
317
- }
318
- const prefix = viaApiBase ? '/api/asset' : '/asset';
319
- const url = `${this.baseURL}${prefix}/${encodeURIComponent(domain)}/${cleanedPath}`;
320
- const response = await fetch(url, {
321
- method: 'GET',
322
- headers: {
323
- Accept: '*/*'
324
- }
325
- });
326
- if (!response.ok) {
327
- throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
328
- }
329
- return response.arrayBuffer();
330
- }
13
+ constructor(baseURL, apiKey) {
14
+ this.baseURL = baseURL;
15
+ this.apiKey = apiKey;
16
+ if (!apiKey || !baseURL) {
17
+ throw new Error('Apikey/api-url required');
18
+ }
19
+ }
20
+ async request(method, command, body) {
21
+ const url = `${this.baseURL}${command}`;
22
+ const headers = {
23
+ Accept: 'application/json',
24
+ Authorization: `Bearer apikey-${this.apiKey}`
25
+ };
26
+ const options = {
27
+ method,
28
+ headers
29
+ };
30
+ // Avoid GET bodies (they're non-standard and can break under some proxies).
31
+ if (method !== 'GET' && body !== undefined) {
32
+ headers['Content-Type'] = 'application/json';
33
+ options.body = JSON.stringify(body);
34
+ }
35
+ const response = await fetch(url, options);
36
+ const j = await response.json();
37
+ if (response.ok) {
38
+ return j;
39
+ }
40
+ if (j && j.message) {
41
+ throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
42
+ } else {
43
+ throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
44
+ }
45
+ }
46
+ async get(command) {
47
+ return this.request('GET', command);
48
+ }
49
+ async post(command, body) {
50
+ return this.request('POST', command, body);
51
+ }
52
+ async put(command, body) {
53
+ return this.request('PUT', command, body);
54
+ }
55
+ async delete(command, body) {
56
+ return this.request('DELETE', command, body);
57
+ }
58
+ validateEmails(list) {
59
+ const valid = [],
60
+ invalid = [];
61
+ const emails = list
62
+ .split(',')
63
+ .map((email) => email.trim())
64
+ .filter((email) => email !== '');
65
+ emails.forEach((email) => {
66
+ const parsed = email_addresses_1.default.parseOneAddress(email);
67
+ if (parsed && parsed.address) {
68
+ valid.push(parsed.address);
69
+ } else {
70
+ invalid.push(email);
71
+ }
72
+ });
73
+ return { valid, invalid };
74
+ }
75
+ validateTemplate(template) {
76
+ try {
77
+ const env = new nunjucks_1.default.Environment(null, { autoescape: true });
78
+ const compiled = nunjucks_1.default.compile(template, env);
79
+ compiled.render({});
80
+ } catch (error) {
81
+ const message = error instanceof Error ? error.message : String(error);
82
+ // Syntax validation should not require local template loaders.
83
+ if (/template not found|no loader|unable to find template/i.test(message)) {
84
+ return;
85
+ }
86
+ if (error instanceof Error) {
87
+ throw new Error(`Template validation failed: ${error.message}`);
88
+ } else {
89
+ throw new Error('Template validation failed with an unknown error');
90
+ }
91
+ }
92
+ }
93
+ validateSender(sender) {
94
+ const exp = /^[^<>]+<[^<>]+@[^<>]+\.[^<>]+>$/;
95
+ if (!exp.test(sender)) {
96
+ throw new Error('Invalid sender format. Expected "Name <email@example.com>"');
97
+ }
98
+ }
99
+ createAttachmentPayload(attachments) {
100
+ const formData = new FormData();
101
+ const usedFields = [];
102
+ for (const attachment of attachments) {
103
+ if (!attachment?.path) {
104
+ throw new Error('Attachment path is required');
105
+ }
106
+ const raw = fs_1.default.readFileSync(attachment.path);
107
+ const filename = attachment.filename || path_1.default.basename(attachment.path);
108
+ const blob = new Blob([raw], attachment.contentType ? { type: attachment.contentType } : undefined);
109
+ const field = attachment.field || 'attachment';
110
+ formData.append(field, blob, filename);
111
+ usedFields.push(field);
112
+ }
113
+ return { formData, usedFields };
114
+ }
115
+ appendFields(formData, fields) {
116
+ for (const [key, value] of Object.entries(fields)) {
117
+ if (value === undefined || value === null) {
118
+ continue;
119
+ }
120
+ if (typeof value === 'string') {
121
+ formData.append(key, value);
122
+ } else if (typeof value === 'number' || typeof value === 'boolean') {
123
+ formData.append(key, String(value));
124
+ } else {
125
+ formData.append(key, JSON.stringify(value));
126
+ }
127
+ }
128
+ }
129
+ async postFormData(command, formData) {
130
+ const url = `${this.baseURL}${command}`;
131
+ const response = await fetch(url, {
132
+ method: 'POST',
133
+ headers: {
134
+ Authorization: `Bearer apikey-${this.apiKey}`
135
+ },
136
+ body: formData
137
+ });
138
+ const j = await response.json();
139
+ if (response.ok) {
140
+ return j;
141
+ }
142
+ if (j && j.message) {
143
+ throw new Error(`FETCH FAILED: ${response.status} ${j.message}`);
144
+ }
145
+ throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
146
+ }
147
+ async storeTemplate(td) {
148
+ // Backward-compatible alias for transactional template storage.
149
+ return this.storeTxTemplate(td);
150
+ }
151
+ async sendTemplate(std) {
152
+ if (!std.name || !std.rcpt) {
153
+ throw new Error('Invalid request body; name/rcpt required');
154
+ }
155
+ return this.sendTxMessage(std);
156
+ }
157
+ async storeTxTemplate(td) {
158
+ if (!td.template) {
159
+ throw new Error('No template data provided');
160
+ }
161
+ this.validateTemplate(td.template);
162
+ if (td.sender) {
163
+ this.validateSender(td.sender);
164
+ }
165
+ return this.post('/api/v1/tx/template', td);
166
+ }
167
+ async sendTxMessage(std) {
168
+ if (!std.name || !std.rcpt) {
169
+ throw new Error('Invalid request body; name/rcpt required');
170
+ }
171
+ const { invalid } = this.validateEmails(std.rcpt);
172
+ if (invalid.length > 0) {
173
+ throw new Error('Invalid email address(es): ' + invalid.join(','));
174
+ }
175
+ const body = {
176
+ name: std.name,
177
+ rcpt: std.rcpt,
178
+ domain: std.domain || '',
179
+ locale: std.locale || '',
180
+ vars: std.vars || {},
181
+ replyTo: std.replyTo,
182
+ headers: std.headers
183
+ };
184
+ if (std.attachments && std.attachments.length > 0) {
185
+ if (std.headers) {
186
+ throw new Error('Headers are not supported with attachment uploads');
187
+ }
188
+ const { formData } = this.createAttachmentPayload(std.attachments);
189
+ this.appendFields(formData, {
190
+ name: std.name,
191
+ rcpt: std.rcpt,
192
+ domain: std.domain || '',
193
+ locale: std.locale || '',
194
+ vars: JSON.stringify(std.vars || {}),
195
+ replyTo: std.replyTo
196
+ });
197
+ return this.postFormData('/api/v1/tx/message', formData);
198
+ }
199
+ return this.post('/api/v1/tx/message', body);
200
+ }
201
+ async storeFormTemplate(data) {
202
+ if (!data.template) {
203
+ throw new Error('No template data provided');
204
+ }
205
+ if (!data.idname) {
206
+ throw new Error('Missing form identifier');
207
+ }
208
+ if (!data.sender) {
209
+ throw new Error('Missing sender address');
210
+ }
211
+ if (!data.recipient) {
212
+ throw new Error('Missing recipient address');
213
+ }
214
+ this.validateTemplate(data.template);
215
+ this.validateSender(data.sender);
216
+ return this.post('/api/v1/form/template', data);
217
+ }
218
+ async storeFormRecipient(data) {
219
+ if (!data.domain) {
220
+ throw new Error('Missing domain');
221
+ }
222
+ if (!data.idname) {
223
+ throw new Error('Missing recipient identifier');
224
+ }
225
+ if (!data.email) {
226
+ throw new Error('Missing recipient email');
227
+ }
228
+ const parsed = email_addresses_1.default.parseOneAddress(data.email);
229
+ if (!parsed || !parsed.address) {
230
+ throw new Error('Invalid recipient email address');
231
+ }
232
+ return this.post('/api/v1/form/recipient', data);
233
+ }
234
+ async sendFormMessage(data) {
235
+ if (!data._mm_form_key) {
236
+ throw new Error('Invalid request body; _mm_form_key required');
237
+ }
238
+ const fields = data.fields || {};
239
+ const baseFields = {
240
+ _mm_form_key: data._mm_form_key,
241
+ _mm_locale: data._mm_locale,
242
+ _mm_recipients: data._mm_recipients,
243
+ ...fields
244
+ };
245
+ if (data.attachments && data.attachments.length > 0) {
246
+ const normalized = data.attachments.map((attachment, idx) => {
247
+ const field = attachment.field || `_mm_file${idx + 1}`;
248
+ if (!field.startsWith('_mm_file')) {
249
+ throw new Error('Form attachments must use multipart field names starting with _mm_file');
250
+ }
251
+ return { ...attachment, field };
252
+ });
253
+ const { formData } = this.createAttachmentPayload(normalized);
254
+ this.appendFields(formData, {
255
+ _mm_form_key: data._mm_form_key,
256
+ _mm_locale: data._mm_locale,
257
+ _mm_recipients: data._mm_recipients
258
+ });
259
+ this.appendFields(formData, fields);
260
+ return this.postFormData('/api/v1/form/message', formData);
261
+ }
262
+ return this.post('/api/v1/form/message', baseFields);
263
+ }
264
+ async uploadAssets(data) {
265
+ if (!data.domain) {
266
+ throw new Error('domain is required');
267
+ }
268
+ if (!data.files || data.files.length === 0) {
269
+ throw new Error('At least one asset file is required');
270
+ }
271
+ if (data.templateType && !data.template) {
272
+ throw new Error('template is required when templateType is provided');
273
+ }
274
+ if (data.template && !data.templateType) {
275
+ throw new Error('templateType is required when template is provided');
276
+ }
277
+ const attachments = data.files.map((input) => {
278
+ if (typeof input === 'string') {
279
+ return { path: input, field: 'asset' };
280
+ }
281
+ return { ...input, field: input.field || 'asset' };
282
+ });
283
+ const { formData } = this.createAttachmentPayload(attachments);
284
+ this.appendFields(formData, {
285
+ domain: data.domain,
286
+ templateType: data.templateType,
287
+ template: data.template,
288
+ locale: data.locale,
289
+ path: data.path
290
+ });
291
+ return this.postFormData('/api/v1/assets', formData);
292
+ }
293
+ async getSwaggerSpec() {
294
+ return this.get('/api/swagger');
295
+ }
296
+ async fetchPublicAsset(domain, assetPath, viaApiBase = false) {
297
+ if (!domain) {
298
+ throw new Error('domain is required');
299
+ }
300
+ if (!assetPath) {
301
+ throw new Error('assetPath is required');
302
+ }
303
+ const cleanedPath = assetPath
304
+ .split('/')
305
+ .filter(Boolean)
306
+ .map((segment) => encodeURIComponent(segment))
307
+ .join('/');
308
+ if (!cleanedPath) {
309
+ throw new Error('assetPath is required');
310
+ }
311
+ const prefix = viaApiBase ? '/api/asset' : '/asset';
312
+ const url = `${this.baseURL}${prefix}/${encodeURIComponent(domain)}/${cleanedPath}`;
313
+ const response = await fetch(url, {
314
+ method: 'GET',
315
+ headers: {
316
+ Accept: '*/*'
317
+ }
318
+ });
319
+ if (!response.ok) {
320
+ throw new Error(`FETCH FAILED: ${response.status} ${response.statusText}`);
321
+ }
322
+ return response.arrayBuffer();
323
+ }
331
324
  }
332
325
  exports.default = templateClient;