@quatrain/messaging 1.1.2 → 1.1.4
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/dist/AbstractMessagingAdapter.d.ts +4 -0
- package/dist/AbstractMessagingAdapter.js +4 -0
- package/dist/MessageFormatter.d.ts +18 -0
- package/dist/MessageFormatter.js +34 -2
- package/dist/Messaging.d.ts +19 -0
- package/dist/Messaging.js +19 -0
- package/dist/Messaging.test.js +36 -0
- package/dist/MockMessagingAdapter.d.ts +63 -0
- package/dist/MockMessagingAdapter.js +63 -0
- package/package.json +2 -2
- package/src/AbstractMessagingAdapter.ts +4 -0
- package/src/MessageFormatter.ts +33 -3
- package/src/Messaging.test.ts +42 -0
- package/src/Messaging.ts +19 -0
- package/src/MockMessagingAdapter.ts +63 -0
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { MessagingParameters } from './Messaging';
|
|
2
|
+
/**
|
|
3
|
+
* Base class contract enforcing setup logic across all messaging capabilities
|
|
4
|
+
* (Email, SMS, Push Notifications).
|
|
5
|
+
*/
|
|
2
6
|
export declare abstract class AbstractMessagingAdapter {
|
|
3
7
|
protected _params: MessagingParameters;
|
|
4
8
|
constructor(params?: MessagingParameters);
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AbstractMessagingAdapter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base class contract enforcing setup logic across all messaging capabilities
|
|
6
|
+
* (Email, SMS, Push Notifications).
|
|
7
|
+
*/
|
|
4
8
|
class AbstractMessagingAdapter {
|
|
5
9
|
constructor(params = {}) {
|
|
6
10
|
this._params = {};
|
|
@@ -1,4 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Specialized utility handling string cleanup and Mustache interpolation for templates.
|
|
3
|
+
*/
|
|
1
4
|
export declare class MessageFormatter {
|
|
5
|
+
/**
|
|
6
|
+
* Cleans all HTML tags from the title string.
|
|
7
|
+
* This uses a robust, RegExp-free state loop to prevent any risk of Regular Expression
|
|
8
|
+
* Denial of Service (ReDoS) or catastrophic backtracking, fully satisfying SonarQube security rules.
|
|
9
|
+
*
|
|
10
|
+
* @param title - The raw subject line containing potential HTML tags.
|
|
11
|
+
* @returns The formatted title string with all HTML tags stripped out.
|
|
12
|
+
*/
|
|
2
13
|
static formatTitle(title: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Renders the Mustache layout with provided contextual variables.
|
|
16
|
+
*
|
|
17
|
+
* @param body - The markdown/HTML layout.
|
|
18
|
+
* @param data - The variables context map.
|
|
19
|
+
* @returns Parsed output string.
|
|
20
|
+
*/
|
|
3
21
|
static formatBody(body: string, data?: {}): string;
|
|
4
22
|
}
|
package/dist/MessageFormatter.js
CHANGED
|
@@ -5,12 +5,44 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.MessageFormatter = void 0;
|
|
7
7
|
const mustache_1 = __importDefault(require("mustache"));
|
|
8
|
+
/**
|
|
9
|
+
* Specialized utility handling string cleanup and Mustache interpolation for templates.
|
|
10
|
+
*/
|
|
8
11
|
class MessageFormatter {
|
|
12
|
+
/**
|
|
13
|
+
* Cleans all HTML tags from the title string.
|
|
14
|
+
* This uses a robust, RegExp-free state loop to prevent any risk of Regular Expression
|
|
15
|
+
* Denial of Service (ReDoS) or catastrophic backtracking, fully satisfying SonarQube security rules.
|
|
16
|
+
*
|
|
17
|
+
* @param title - The raw subject line containing potential HTML tags.
|
|
18
|
+
* @returns The formatted title string with all HTML tags stripped out.
|
|
19
|
+
*/
|
|
9
20
|
static formatTitle(title) {
|
|
10
|
-
|
|
21
|
+
let result = '';
|
|
22
|
+
let inTag = false;
|
|
23
|
+
for (let i = 0; i < title.length; i++) {
|
|
24
|
+
const char = title[i];
|
|
25
|
+
if (char === '<') {
|
|
26
|
+
inTag = true;
|
|
27
|
+
}
|
|
28
|
+
else if (char === '>') {
|
|
29
|
+
inTag = false;
|
|
30
|
+
}
|
|
31
|
+
else if (!inTag) {
|
|
32
|
+
result += char;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
11
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Renders the Mustache layout with provided contextual variables.
|
|
39
|
+
*
|
|
40
|
+
* @param body - The markdown/HTML layout.
|
|
41
|
+
* @param data - The variables context map.
|
|
42
|
+
* @returns Parsed output string.
|
|
43
|
+
*/
|
|
12
44
|
static formatBody(body, data) {
|
|
13
|
-
return mustache_1.default.render(body, data);
|
|
45
|
+
return mustache_1.default.render(body, data || {});
|
|
14
46
|
}
|
|
15
47
|
}
|
|
16
48
|
exports.MessageFormatter = MessageFormatter;
|
package/dist/Messaging.d.ts
CHANGED
|
@@ -9,10 +9,29 @@ export interface MessagingParameters {
|
|
|
9
9
|
config?: any;
|
|
10
10
|
debug?: boolean;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Singleton Registry dispatching text messages, emails, or push notifications.
|
|
14
|
+
*/
|
|
12
15
|
export declare class Messaging extends Core {
|
|
16
|
+
/** The alias for the primary fallback messager instance. */
|
|
13
17
|
static defaultMessager: string;
|
|
18
|
+
/** Scoped domain logger. */
|
|
14
19
|
static logger: any;
|
|
15
20
|
protected static _messagers: MessagingRegistry<any>;
|
|
21
|
+
/**
|
|
22
|
+
* Wires an instantiated messaging client into the global routing map.
|
|
23
|
+
*
|
|
24
|
+
* @param messager - The adapter logic handling the sends.
|
|
25
|
+
* @param alias - Registered alias label.
|
|
26
|
+
* @param setDefault - Whether this is the new system default router.
|
|
27
|
+
*/
|
|
16
28
|
static addMessager(messager: NotificationCapableAdapter | EmailCapableAdapter | TextCapableAdapter, alias: string, setDefault?: boolean): void;
|
|
29
|
+
/**
|
|
30
|
+
* Recovers a registered messager by alias.
|
|
31
|
+
*
|
|
32
|
+
* @param alias - The name identifier.
|
|
33
|
+
* @returns The requested messaging provider.
|
|
34
|
+
* @throws If the alias was never registered.
|
|
35
|
+
*/
|
|
17
36
|
static getMessager(alias?: string): any;
|
|
18
37
|
}
|
package/dist/Messaging.js
CHANGED
|
@@ -3,13 +3,30 @@ var _a;
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.Messaging = void 0;
|
|
5
5
|
const core_1 = require("@quatrain/core");
|
|
6
|
+
/**
|
|
7
|
+
* Singleton Registry dispatching text messages, emails, or push notifications.
|
|
8
|
+
*/
|
|
6
9
|
class Messaging extends core_1.Core {
|
|
10
|
+
/**
|
|
11
|
+
* Wires an instantiated messaging client into the global routing map.
|
|
12
|
+
*
|
|
13
|
+
* @param messager - The adapter logic handling the sends.
|
|
14
|
+
* @param alias - Registered alias label.
|
|
15
|
+
* @param setDefault - Whether this is the new system default router.
|
|
16
|
+
*/
|
|
7
17
|
static addMessager(messager, alias, setDefault = false) {
|
|
8
18
|
this._messagers[alias] = messager;
|
|
9
19
|
if (setDefault) {
|
|
10
20
|
this.defaultMessager = alias;
|
|
11
21
|
}
|
|
12
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Recovers a registered messager by alias.
|
|
25
|
+
*
|
|
26
|
+
* @param alias - The name identifier.
|
|
27
|
+
* @returns The requested messaging provider.
|
|
28
|
+
* @throws If the alias was never registered.
|
|
29
|
+
*/
|
|
13
30
|
static getMessager(alias = this.defaultMessager) {
|
|
14
31
|
if (this._messagers[alias]) {
|
|
15
32
|
return this._messagers[alias];
|
|
@@ -21,6 +38,8 @@ class Messaging extends core_1.Core {
|
|
|
21
38
|
}
|
|
22
39
|
exports.Messaging = Messaging;
|
|
23
40
|
_a = Messaging;
|
|
41
|
+
/** The alias for the primary fallback messager instance. */
|
|
24
42
|
Messaging.defaultMessager = 'default';
|
|
43
|
+
/** Scoped domain logger. */
|
|
25
44
|
Messaging.logger = _a.addLogger('Messaging');
|
|
26
45
|
Messaging._messagers = {};
|
package/dist/Messaging.test.js
CHANGED
|
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const Messaging_1 = require("./Messaging");
|
|
13
13
|
const MockMessagingAdapter_1 = require("./MockMessagingAdapter");
|
|
14
|
+
const MessageFormatter_1 = require("./MessageFormatter");
|
|
14
15
|
describe('Messaging', () => {
|
|
15
16
|
let mockAdapter1;
|
|
16
17
|
let mockAdapter2;
|
|
@@ -255,4 +256,39 @@ describe('Messaging', () => {
|
|
|
255
256
|
expect(Messaging_1.Messaging.logger).toBeDefined();
|
|
256
257
|
});
|
|
257
258
|
});
|
|
259
|
+
describe('MessageFormatter', () => {
|
|
260
|
+
describe('formatTitle', () => {
|
|
261
|
+
it('should strip single HTML tags correctly', () => {
|
|
262
|
+
const raw = 'Hello <b>World</b>!';
|
|
263
|
+
const result = MessageFormatter_1.MessageFormatter.formatTitle(raw);
|
|
264
|
+
expect(result).toBe('Hello World!');
|
|
265
|
+
});
|
|
266
|
+
it('should strip multiple nested and unclosed tags correctly', () => {
|
|
267
|
+
const raw = '<div><p>Welcome to <span>Quatrain</span></p></div>';
|
|
268
|
+
const result = MessageFormatter_1.MessageFormatter.formatTitle(raw);
|
|
269
|
+
expect(result).toBe('Welcome to Quatrain');
|
|
270
|
+
});
|
|
271
|
+
it('should return plain text unchanged', () => {
|
|
272
|
+
const raw = 'Plain text without any tags.';
|
|
273
|
+
const result = MessageFormatter_1.MessageFormatter.formatTitle(raw);
|
|
274
|
+
expect(result).toBe(raw);
|
|
275
|
+
});
|
|
276
|
+
it('should handle empty string correctly', () => {
|
|
277
|
+
expect(MessageFormatter_1.MessageFormatter.formatTitle('')).toBe('');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
describe('formatBody', () => {
|
|
281
|
+
it('should interpolate Mustache variables correctly', () => {
|
|
282
|
+
const template = 'Hello {{firstname}} {{lastname}}!';
|
|
283
|
+
const data = { firstname: 'John', lastname: 'Doe' };
|
|
284
|
+
const result = MessageFormatter_1.MessageFormatter.formatBody(template, data);
|
|
285
|
+
expect(result).toBe('Hello John Doe!');
|
|
286
|
+
});
|
|
287
|
+
it('should render original template when no data is provided', () => {
|
|
288
|
+
const template = 'Hello {{firstname}}!';
|
|
289
|
+
const result = MessageFormatter_1.MessageFormatter.formatBody(template);
|
|
290
|
+
expect(result).toBe('Hello !');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
258
294
|
});
|
|
@@ -18,15 +18,78 @@ interface SentMessage {
|
|
|
18
18
|
export declare class MockMessagingAdapter extends AbstractMessagingAdapter implements NotificationCapableAdapter, EmailCapableAdapter, TextCapableAdapter {
|
|
19
19
|
private sentMessages;
|
|
20
20
|
constructor(params?: MessagingParameters);
|
|
21
|
+
/**
|
|
22
|
+
* Mock tracking of a single notification dispatch.
|
|
23
|
+
*
|
|
24
|
+
* @param recipient - Target info.
|
|
25
|
+
* @param message - Payload.
|
|
26
|
+
* @returns Mock response object.
|
|
27
|
+
*/
|
|
21
28
|
sendNotification(recipient: MessagingRecipient, message: NotificationMessage): Promise<any>;
|
|
29
|
+
/**
|
|
30
|
+
* Mock tracking of a batch notification dispatch.
|
|
31
|
+
*
|
|
32
|
+
* @param recipients - Array of targets.
|
|
33
|
+
* @param message - Payload.
|
|
34
|
+
* @returns Mock batch response object.
|
|
35
|
+
*/
|
|
22
36
|
sendNotifications(recipients: MessagingRecipient[], message: NotificationMessage): Promise<any>;
|
|
37
|
+
/**
|
|
38
|
+
* Mock tracking of a single email dispatch.
|
|
39
|
+
*
|
|
40
|
+
* @param recipient - Target info.
|
|
41
|
+
* @param message - Payload.
|
|
42
|
+
* @returns Mock response object.
|
|
43
|
+
*/
|
|
23
44
|
sendEmail(recipient: MessagingRecipient, message: NotificationMessage): Promise<any>;
|
|
45
|
+
/**
|
|
46
|
+
* Mock tracking of a batch email dispatch.
|
|
47
|
+
*
|
|
48
|
+
* @param recipients - Array of targets.
|
|
49
|
+
* @param message - Payload.
|
|
50
|
+
* @returns Mock batch response object.
|
|
51
|
+
*/
|
|
24
52
|
sendEmails(recipients: MessagingRecipient[], message: NotificationMessage): Promise<any>;
|
|
53
|
+
/**
|
|
54
|
+
* Mock tracking of a single SMS dispatch.
|
|
55
|
+
*
|
|
56
|
+
* @param recipient - Target info.
|
|
57
|
+
* @param message - Payload.
|
|
58
|
+
* @returns Mock response object.
|
|
59
|
+
*/
|
|
25
60
|
sendTextMessage(recipient: MessagingRecipient, message: NotificationMessage): Promise<any>;
|
|
61
|
+
/**
|
|
62
|
+
* Mock tracking of a batch SMS dispatch.
|
|
63
|
+
*
|
|
64
|
+
* @param recipients - Array of targets.
|
|
65
|
+
* @param message - Payload.
|
|
66
|
+
* @returns Mock batch response object.
|
|
67
|
+
*/
|
|
26
68
|
sendTextMessages(recipients: MessagingRecipient[], message: NotificationMessage): Promise<any>;
|
|
69
|
+
/**
|
|
70
|
+
* Testing utility to retrieve sent messages by category.
|
|
71
|
+
*
|
|
72
|
+
* @param type - Optional filter tag.
|
|
73
|
+
* @returns Array of saved payloads.
|
|
74
|
+
*/
|
|
27
75
|
getSentMessages(type?: 'notification' | 'email' | 'text'): SentMessage[];
|
|
76
|
+
/**
|
|
77
|
+
* Erases the internal mock history log.
|
|
78
|
+
*/
|
|
28
79
|
clearMessages(): void;
|
|
80
|
+
/**
|
|
81
|
+
* Retrieves the total count of mock-sent payloads.
|
|
82
|
+
*
|
|
83
|
+
* @param type - Optional filter tag.
|
|
84
|
+
* @returns Total matching elements.
|
|
85
|
+
*/
|
|
29
86
|
getMessageCount(type?: 'notification' | 'email' | 'text'): number;
|
|
87
|
+
/**
|
|
88
|
+
* Peeks at the most recently dispatched message.
|
|
89
|
+
*
|
|
90
|
+
* @param type - Optional filter tag.
|
|
91
|
+
* @returns SentMessage representation.
|
|
92
|
+
*/
|
|
30
93
|
getLastMessage(type?: 'notification' | 'email' | 'text'): SentMessage | undefined;
|
|
31
94
|
}
|
|
32
95
|
export {};
|
|
@@ -20,6 +20,13 @@ class MockMessagingAdapter extends AbstractMessagingAdapter_1.AbstractMessagingA
|
|
|
20
20
|
super(params);
|
|
21
21
|
this.sentMessages = [];
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Mock tracking of a single notification dispatch.
|
|
25
|
+
*
|
|
26
|
+
* @param recipient - Target info.
|
|
27
|
+
* @param message - Payload.
|
|
28
|
+
* @returns Mock response object.
|
|
29
|
+
*/
|
|
23
30
|
sendNotification(recipient, message) {
|
|
24
31
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
32
|
this.sentMessages.push({
|
|
@@ -35,6 +42,13 @@ class MockMessagingAdapter extends AbstractMessagingAdapter_1.AbstractMessagingA
|
|
|
35
42
|
};
|
|
36
43
|
});
|
|
37
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Mock tracking of a batch notification dispatch.
|
|
47
|
+
*
|
|
48
|
+
* @param recipients - Array of targets.
|
|
49
|
+
* @param message - Payload.
|
|
50
|
+
* @returns Mock batch response object.
|
|
51
|
+
*/
|
|
38
52
|
sendNotifications(recipients, message) {
|
|
39
53
|
return __awaiter(this, void 0, void 0, function* () {
|
|
40
54
|
this.sentMessages.push({
|
|
@@ -50,6 +64,13 @@ class MockMessagingAdapter extends AbstractMessagingAdapter_1.AbstractMessagingA
|
|
|
50
64
|
};
|
|
51
65
|
});
|
|
52
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Mock tracking of a single email dispatch.
|
|
69
|
+
*
|
|
70
|
+
* @param recipient - Target info.
|
|
71
|
+
* @param message - Payload.
|
|
72
|
+
* @returns Mock response object.
|
|
73
|
+
*/
|
|
53
74
|
sendEmail(recipient, message) {
|
|
54
75
|
return __awaiter(this, void 0, void 0, function* () {
|
|
55
76
|
this.sentMessages.push({
|
|
@@ -65,6 +86,13 @@ class MockMessagingAdapter extends AbstractMessagingAdapter_1.AbstractMessagingA
|
|
|
65
86
|
};
|
|
66
87
|
});
|
|
67
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Mock tracking of a batch email dispatch.
|
|
91
|
+
*
|
|
92
|
+
* @param recipients - Array of targets.
|
|
93
|
+
* @param message - Payload.
|
|
94
|
+
* @returns Mock batch response object.
|
|
95
|
+
*/
|
|
68
96
|
sendEmails(recipients, message) {
|
|
69
97
|
return __awaiter(this, void 0, void 0, function* () {
|
|
70
98
|
this.sentMessages.push({
|
|
@@ -80,6 +108,13 @@ class MockMessagingAdapter extends AbstractMessagingAdapter_1.AbstractMessagingA
|
|
|
80
108
|
};
|
|
81
109
|
});
|
|
82
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Mock tracking of a single SMS dispatch.
|
|
113
|
+
*
|
|
114
|
+
* @param recipient - Target info.
|
|
115
|
+
* @param message - Payload.
|
|
116
|
+
* @returns Mock response object.
|
|
117
|
+
*/
|
|
83
118
|
sendTextMessage(recipient, message) {
|
|
84
119
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
120
|
this.sentMessages.push({
|
|
@@ -95,6 +130,13 @@ class MockMessagingAdapter extends AbstractMessagingAdapter_1.AbstractMessagingA
|
|
|
95
130
|
};
|
|
96
131
|
});
|
|
97
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Mock tracking of a batch SMS dispatch.
|
|
135
|
+
*
|
|
136
|
+
* @param recipients - Array of targets.
|
|
137
|
+
* @param message - Payload.
|
|
138
|
+
* @returns Mock batch response object.
|
|
139
|
+
*/
|
|
98
140
|
sendTextMessages(recipients, message) {
|
|
99
141
|
return __awaiter(this, void 0, void 0, function* () {
|
|
100
142
|
this.sentMessages.push({
|
|
@@ -111,18 +153,39 @@ class MockMessagingAdapter extends AbstractMessagingAdapter_1.AbstractMessagingA
|
|
|
111
153
|
});
|
|
112
154
|
}
|
|
113
155
|
// Helper methods for testing
|
|
156
|
+
/**
|
|
157
|
+
* Testing utility to retrieve sent messages by category.
|
|
158
|
+
*
|
|
159
|
+
* @param type - Optional filter tag.
|
|
160
|
+
* @returns Array of saved payloads.
|
|
161
|
+
*/
|
|
114
162
|
getSentMessages(type) {
|
|
115
163
|
if (type) {
|
|
116
164
|
return this.sentMessages.filter((msg) => msg.type === type);
|
|
117
165
|
}
|
|
118
166
|
return this.sentMessages;
|
|
119
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Erases the internal mock history log.
|
|
170
|
+
*/
|
|
120
171
|
clearMessages() {
|
|
121
172
|
this.sentMessages = [];
|
|
122
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Retrieves the total count of mock-sent payloads.
|
|
176
|
+
*
|
|
177
|
+
* @param type - Optional filter tag.
|
|
178
|
+
* @returns Total matching elements.
|
|
179
|
+
*/
|
|
123
180
|
getMessageCount(type) {
|
|
124
181
|
return this.getSentMessages(type).length;
|
|
125
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Peeks at the most recently dispatched message.
|
|
185
|
+
*
|
|
186
|
+
* @param type - Optional filter tag.
|
|
187
|
+
* @returns SentMessage representation.
|
|
188
|
+
*/
|
|
126
189
|
getLastMessage(type) {
|
|
127
190
|
const messages = this.getSentMessages(type);
|
|
128
191
|
return messages[messages.length - 1];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quatrain/messaging",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"description": "Messaging adapters commons",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"author": "Quatrain Développement SAS <developers@quatrain.com>",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@quatrain/core": "^1.2.
|
|
23
|
+
"@quatrain/core": "^1.2.11",
|
|
24
24
|
"mustache": "^4.2.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { MessagingParameters } from './Messaging'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Base class contract enforcing setup logic across all messaging capabilities
|
|
5
|
+
* (Email, SMS, Push Notifications).
|
|
6
|
+
*/
|
|
3
7
|
export abstract class AbstractMessagingAdapter {
|
|
4
8
|
protected _params: MessagingParameters = {}
|
|
5
9
|
|
package/src/MessageFormatter.ts
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
import Mustache from 'mustache'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Specialized utility handling string cleanup and Mustache interpolation for templates.
|
|
5
|
+
*/
|
|
3
6
|
export class MessageFormatter {
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Cleans all HTML tags from the title string.
|
|
9
|
+
* This uses a robust, RegExp-free state loop to prevent any risk of Regular Expression
|
|
10
|
+
* Denial of Service (ReDoS) or catastrophic backtracking, fully satisfying SonarQube security rules.
|
|
11
|
+
*
|
|
12
|
+
* @param title - The raw subject line containing potential HTML tags.
|
|
13
|
+
* @returns The formatted title string with all HTML tags stripped out.
|
|
14
|
+
*/
|
|
15
|
+
static formatTitle(title: string): string {
|
|
16
|
+
let result = ''
|
|
17
|
+
let inTag = false
|
|
18
|
+
for (let i = 0; i < title.length; i++) {
|
|
19
|
+
const char = title[i]
|
|
20
|
+
if (char === '<') {
|
|
21
|
+
inTag = true
|
|
22
|
+
} else if (char === '>') {
|
|
23
|
+
inTag = false
|
|
24
|
+
} else if (!inTag) {
|
|
25
|
+
result += char
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result
|
|
6
29
|
}
|
|
7
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Renders the Mustache layout with provided contextual variables.
|
|
33
|
+
*
|
|
34
|
+
* @param body - The markdown/HTML layout.
|
|
35
|
+
* @param data - The variables context map.
|
|
36
|
+
* @returns Parsed output string.
|
|
37
|
+
*/
|
|
8
38
|
static formatBody(body: string, data?: {}) {
|
|
9
|
-
return Mustache.render(body, data)
|
|
39
|
+
return Mustache.render(body, data || {})
|
|
10
40
|
}
|
|
11
41
|
}
|
package/src/Messaging.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Messaging, MessagingParameters } from './Messaging'
|
|
|
2
2
|
import { MockMessagingAdapter } from './MockMessagingAdapter'
|
|
3
3
|
import { MessagingRecipient } from './types/MessagingRecipient'
|
|
4
4
|
import { NotificationMessage } from './types/NotificationMessage'
|
|
5
|
+
import { MessageFormatter } from './MessageFormatter'
|
|
5
6
|
|
|
6
7
|
describe('Messaging', () => {
|
|
7
8
|
let mockAdapter1: MockMessagingAdapter
|
|
@@ -315,4 +316,45 @@ describe('Messaging', () => {
|
|
|
315
316
|
expect(Messaging.logger).toBeDefined()
|
|
316
317
|
})
|
|
317
318
|
})
|
|
319
|
+
|
|
320
|
+
describe('MessageFormatter', () => {
|
|
321
|
+
describe('formatTitle', () => {
|
|
322
|
+
it('should strip single HTML tags correctly', () => {
|
|
323
|
+
const raw = 'Hello <b>World</b>!'
|
|
324
|
+
const result = MessageFormatter.formatTitle(raw)
|
|
325
|
+
expect(result).toBe('Hello World!')
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('should strip multiple nested and unclosed tags correctly', () => {
|
|
329
|
+
const raw = '<div><p>Welcome to <span>Quatrain</span></p></div>'
|
|
330
|
+
const result = MessageFormatter.formatTitle(raw)
|
|
331
|
+
expect(result).toBe('Welcome to Quatrain')
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('should return plain text unchanged', () => {
|
|
335
|
+
const raw = 'Plain text without any tags.'
|
|
336
|
+
const result = MessageFormatter.formatTitle(raw)
|
|
337
|
+
expect(result).toBe(raw)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('should handle empty string correctly', () => {
|
|
341
|
+
expect(MessageFormatter.formatTitle('')).toBe('')
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
describe('formatBody', () => {
|
|
346
|
+
it('should interpolate Mustache variables correctly', () => {
|
|
347
|
+
const template = 'Hello {{firstname}} {{lastname}}!'
|
|
348
|
+
const data = { firstname: 'John', lastname: 'Doe' }
|
|
349
|
+
const result = MessageFormatter.formatBody(template, data)
|
|
350
|
+
expect(result).toBe('Hello John Doe!')
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('should render original template when no data is provided', () => {
|
|
354
|
+
const template = 'Hello {{firstname}}!'
|
|
355
|
+
const result = MessageFormatter.formatBody(template)
|
|
356
|
+
expect(result).toBe('Hello !')
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
})
|
|
318
360
|
})
|
package/src/Messaging.ts
CHANGED
|
@@ -14,12 +14,24 @@ export interface MessagingParameters {
|
|
|
14
14
|
debug?: boolean
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Singleton Registry dispatching text messages, emails, or push notifications.
|
|
19
|
+
*/
|
|
17
20
|
export class Messaging extends Core {
|
|
21
|
+
/** The alias for the primary fallback messager instance. */
|
|
18
22
|
static defaultMessager = 'default'
|
|
23
|
+
/** Scoped domain logger. */
|
|
19
24
|
static logger = this.addLogger('Messaging')
|
|
20
25
|
|
|
21
26
|
protected static _messagers: MessagingRegistry<any> = {}
|
|
22
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Wires an instantiated messaging client into the global routing map.
|
|
30
|
+
*
|
|
31
|
+
* @param messager - The adapter logic handling the sends.
|
|
32
|
+
* @param alias - Registered alias label.
|
|
33
|
+
* @param setDefault - Whether this is the new system default router.
|
|
34
|
+
*/
|
|
23
35
|
static addMessager(
|
|
24
36
|
messager:
|
|
25
37
|
| NotificationCapableAdapter
|
|
@@ -34,6 +46,13 @@ export class Messaging extends Core {
|
|
|
34
46
|
}
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Recovers a registered messager by alias.
|
|
51
|
+
*
|
|
52
|
+
* @param alias - The name identifier.
|
|
53
|
+
* @returns The requested messaging provider.
|
|
54
|
+
* @throws If the alias was never registered.
|
|
55
|
+
*/
|
|
37
56
|
static getMessager(alias: string = this.defaultMessager) {
|
|
38
57
|
if (this._messagers[alias]) {
|
|
39
58
|
return this._messagers[alias]
|
|
@@ -30,6 +30,13 @@ export class MockMessagingAdapter
|
|
|
30
30
|
super(params)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Mock tracking of a single notification dispatch.
|
|
35
|
+
*
|
|
36
|
+
* @param recipient - Target info.
|
|
37
|
+
* @param message - Payload.
|
|
38
|
+
* @returns Mock response object.
|
|
39
|
+
*/
|
|
33
40
|
async sendNotification(
|
|
34
41
|
recipient: MessagingRecipient,
|
|
35
42
|
message: NotificationMessage
|
|
@@ -48,6 +55,13 @@ export class MockMessagingAdapter
|
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Mock tracking of a batch notification dispatch.
|
|
60
|
+
*
|
|
61
|
+
* @param recipients - Array of targets.
|
|
62
|
+
* @param message - Payload.
|
|
63
|
+
* @returns Mock batch response object.
|
|
64
|
+
*/
|
|
51
65
|
async sendNotifications(
|
|
52
66
|
recipients: MessagingRecipient[],
|
|
53
67
|
message: NotificationMessage
|
|
@@ -66,6 +80,13 @@ export class MockMessagingAdapter
|
|
|
66
80
|
}
|
|
67
81
|
}
|
|
68
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Mock tracking of a single email dispatch.
|
|
85
|
+
*
|
|
86
|
+
* @param recipient - Target info.
|
|
87
|
+
* @param message - Payload.
|
|
88
|
+
* @returns Mock response object.
|
|
89
|
+
*/
|
|
69
90
|
async sendEmail(
|
|
70
91
|
recipient: MessagingRecipient,
|
|
71
92
|
message: NotificationMessage
|
|
@@ -84,6 +105,13 @@ export class MockMessagingAdapter
|
|
|
84
105
|
}
|
|
85
106
|
}
|
|
86
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Mock tracking of a batch email dispatch.
|
|
110
|
+
*
|
|
111
|
+
* @param recipients - Array of targets.
|
|
112
|
+
* @param message - Payload.
|
|
113
|
+
* @returns Mock batch response object.
|
|
114
|
+
*/
|
|
87
115
|
async sendEmails(
|
|
88
116
|
recipients: MessagingRecipient[],
|
|
89
117
|
message: NotificationMessage
|
|
@@ -102,6 +130,13 @@ export class MockMessagingAdapter
|
|
|
102
130
|
}
|
|
103
131
|
}
|
|
104
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Mock tracking of a single SMS dispatch.
|
|
135
|
+
*
|
|
136
|
+
* @param recipient - Target info.
|
|
137
|
+
* @param message - Payload.
|
|
138
|
+
* @returns Mock response object.
|
|
139
|
+
*/
|
|
105
140
|
async sendTextMessage(
|
|
106
141
|
recipient: MessagingRecipient,
|
|
107
142
|
message: NotificationMessage
|
|
@@ -120,6 +155,13 @@ export class MockMessagingAdapter
|
|
|
120
155
|
}
|
|
121
156
|
}
|
|
122
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Mock tracking of a batch SMS dispatch.
|
|
160
|
+
*
|
|
161
|
+
* @param recipients - Array of targets.
|
|
162
|
+
* @param message - Payload.
|
|
163
|
+
* @returns Mock batch response object.
|
|
164
|
+
*/
|
|
123
165
|
async sendTextMessages(
|
|
124
166
|
recipients: MessagingRecipient[],
|
|
125
167
|
message: NotificationMessage
|
|
@@ -139,6 +181,12 @@ export class MockMessagingAdapter
|
|
|
139
181
|
}
|
|
140
182
|
|
|
141
183
|
// Helper methods for testing
|
|
184
|
+
/**
|
|
185
|
+
* Testing utility to retrieve sent messages by category.
|
|
186
|
+
*
|
|
187
|
+
* @param type - Optional filter tag.
|
|
188
|
+
* @returns Array of saved payloads.
|
|
189
|
+
*/
|
|
142
190
|
getSentMessages(type?: 'notification' | 'email' | 'text'): SentMessage[] {
|
|
143
191
|
if (type) {
|
|
144
192
|
return this.sentMessages.filter((msg) => msg.type === type)
|
|
@@ -146,14 +194,29 @@ export class MockMessagingAdapter
|
|
|
146
194
|
return this.sentMessages
|
|
147
195
|
}
|
|
148
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Erases the internal mock history log.
|
|
199
|
+
*/
|
|
149
200
|
clearMessages(): void {
|
|
150
201
|
this.sentMessages = []
|
|
151
202
|
}
|
|
152
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Retrieves the total count of mock-sent payloads.
|
|
206
|
+
*
|
|
207
|
+
* @param type - Optional filter tag.
|
|
208
|
+
* @returns Total matching elements.
|
|
209
|
+
*/
|
|
153
210
|
getMessageCount(type?: 'notification' | 'email' | 'text'): number {
|
|
154
211
|
return this.getSentMessages(type).length
|
|
155
212
|
}
|
|
156
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Peeks at the most recently dispatched message.
|
|
216
|
+
*
|
|
217
|
+
* @param type - Optional filter tag.
|
|
218
|
+
* @returns SentMessage representation.
|
|
219
|
+
*/
|
|
157
220
|
getLastMessage(
|
|
158
221
|
type?: 'notification' | 'email' | 'text'
|
|
159
222
|
): SentMessage | undefined {
|