@xenterprises/fastify-xtwilio 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/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@xenterprises/fastify-xtwilio",
3
+ "type": "module",
4
+ "version": "1.0.0",
5
+ "description": "Fastify plugin for Twilio communications (SMS, Conversations, RCS) and SendGrid email.",
6
+ "main": "src/xTwilio.js",
7
+ "exports": {
8
+ ".": "./src/xTwilio.js"
9
+ },
10
+ "scripts": {
11
+ "start": "fastify start -l info server/app.js",
12
+ "dev": "fastify start -w -l info -P server/app.js",
13
+ "test": "node --test test/xTwilio.test.js"
14
+ },
15
+ "engines": {
16
+ "node": ">=20.0.0",
17
+ "npm": ">=10.0.0"
18
+ },
19
+ "keywords": [
20
+ "fastify",
21
+ "twilio",
22
+ "sms",
23
+ "rcs",
24
+ "conversations",
25
+ "sendgrid",
26
+ "email",
27
+ "communications",
28
+ "messaging",
29
+ "plugin"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/xenterprises/fastify-xtwilio.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/xenterprises/fastify-xtwilio/issues"
37
+ },
38
+ "author": "Tim Mushen",
39
+ "license": "ISC",
40
+ "devDependencies": {
41
+ "@types/node": "^22.7.4",
42
+ "fastify": "^5.1.0",
43
+ "fastify-plugin": "^5.0.0",
44
+ "typescript": "^5.6.3"
45
+ },
46
+ "dependencies": {
47
+ "@sendgrid/client": "^8.1.3",
48
+ "@sendgrid/mail": "^8.1.3",
49
+ "fastify-plugin": "^5.0.0",
50
+ "twilio": "^5.3.2"
51
+ },
52
+ "peerDependencies": {
53
+ "fastify": "^5.0.0"
54
+ }
55
+ }
package/server/app.js ADDED
@@ -0,0 +1,88 @@
1
+ // server/app.js - Example Fastify server using xTwilio
2
+ import Fastify from 'fastify';
3
+ import xTwilio from '../src/xTwilio.js';
4
+
5
+ const fastify = Fastify({
6
+ logger: true,
7
+ });
8
+
9
+ // Register xTwilio plugin
10
+ await fastify.register(xTwilio, {
11
+ twilio: {
12
+ accountSid: process.env.TWILIO_ACCOUNT_SID,
13
+ authToken: process.env.TWILIO_AUTH_TOKEN,
14
+ phoneNumber: process.env.TWILIO_PHONE_NUMBER,
15
+ messagingServiceSid: process.env.TWILIO_MESSAGING_SERVICE_SID,
16
+ },
17
+ sendgrid: {
18
+ apiKey: process.env.SENDGRID_API_KEY,
19
+ fromEmail: process.env.SENDGRID_FROM_EMAIL,
20
+ },
21
+ });
22
+
23
+ // Example SMS route
24
+ fastify.post('/sms/send', async (request, reply) => {
25
+ const { to, body } = request.body;
26
+
27
+ try {
28
+ const result = await fastify.sms.send(to, body);
29
+ return { success: true, messageSid: result.sid };
30
+ } catch (error) {
31
+ reply.code(500);
32
+ return { success: false, error: error.message };
33
+ }
34
+ });
35
+
36
+ // Example Conversation route
37
+ fastify.post('/conversations/create', async (request, reply) => {
38
+ const { friendlyName } = request.body;
39
+
40
+ try {
41
+ const conversation = await fastify.conversations.create(friendlyName);
42
+ return { success: true, conversationSid: conversation.sid };
43
+ } catch (error) {
44
+ reply.code(500);
45
+ return { success: false, error: error.message };
46
+ }
47
+ });
48
+
49
+ // Example RCS route
50
+ fastify.post('/rcs/send-card', async (request, reply) => {
51
+ const { to, card } = request.body;
52
+
53
+ try {
54
+ const result = await fastify.rcs.sendRichCard(to, card);
55
+ return { success: true, messageSid: result.sid };
56
+ } catch (error) {
57
+ reply.code(500);
58
+ return { success: false, error: error.message };
59
+ }
60
+ });
61
+
62
+ // Example Email route
63
+ fastify.post('/email/send', async (request, reply) => {
64
+ const { to, subject, html } = request.body;
65
+
66
+ try {
67
+ const result = await fastify.email.send(to, subject, html);
68
+ return { success: true, messageId: result.messageId };
69
+ } catch (error) {
70
+ reply.code(500);
71
+ return { success: false, error: error.message };
72
+ }
73
+ });
74
+
75
+ // Health check route
76
+ fastify.get('/health', async () => {
77
+ return {
78
+ status: 'ok',
79
+ services: {
80
+ sms: !!fastify.sms,
81
+ conversations: !!fastify.conversations,
82
+ rcs: !!fastify.rcs,
83
+ email: !!fastify.email,
84
+ }
85
+ };
86
+ });
87
+
88
+ export default fastify;
@@ -0,0 +1,328 @@
1
+ // src/services/conversations.js
2
+ import Twilio from "twilio";
3
+
4
+ export async function setupConversations(fastify, options) {
5
+ if (options.active === false) return;
6
+
7
+ // Validate required credentials
8
+ if (!options.accountSid || !options.authToken) {
9
+ throw new Error("Twilio accountSid and authToken must be provided for Conversations.");
10
+ }
11
+
12
+ // Initialize Twilio client
13
+ const twilioClient = Twilio(options.accountSid, options.authToken);
14
+
15
+ fastify.decorate("conversations", {
16
+ /**
17
+ * Create a new conversation
18
+ * @param {string} friendlyName - Human-readable name
19
+ * @param {object} attributes - Optional custom attributes (JSON object)
20
+ * @returns {Promise<object>} Conversation object
21
+ */
22
+ create: async (friendlyName, attributes = {}) => {
23
+ try {
24
+ const conversation = await twilioClient.conversations.v1.conversations.create({
25
+ friendlyName,
26
+ attributes: JSON.stringify(attributes),
27
+ });
28
+ return conversation;
29
+ } catch (error) {
30
+ fastify.log.error("Conversations create failed:", error);
31
+ throw new Error("Failed to create conversation.");
32
+ }
33
+ },
34
+
35
+ /**
36
+ * Get a conversation by SID
37
+ * @param {string} conversationSid - Conversation SID
38
+ * @returns {Promise<object>} Conversation object
39
+ */
40
+ get: async (conversationSid) => {
41
+ try {
42
+ const conversation = await twilioClient.conversations.v1
43
+ .conversations(conversationSid)
44
+ .fetch();
45
+ return conversation;
46
+ } catch (error) {
47
+ fastify.log.error("Conversations get failed:", error);
48
+ throw new Error("Failed to get conversation.");
49
+ }
50
+ },
51
+
52
+ /**
53
+ * Update a conversation
54
+ * @param {string} conversationSid - Conversation SID
55
+ * @param {object} updates - Fields to update (friendlyName, attributes, etc.)
56
+ * @returns {Promise<object>} Updated conversation object
57
+ */
58
+ update: async (conversationSid, updates) => {
59
+ try {
60
+ const conversation = await twilioClient.conversations.v1
61
+ .conversations(conversationSid)
62
+ .update(updates);
63
+ return conversation;
64
+ } catch (error) {
65
+ fastify.log.error("Conversations update failed:", error);
66
+ throw new Error("Failed to update conversation.");
67
+ }
68
+ },
69
+
70
+ /**
71
+ * List all conversations
72
+ * @param {object} filters - Filter options (limit)
73
+ * @returns {Promise<object[]>} List of conversations
74
+ */
75
+ list: async (filters = {}) => {
76
+ try {
77
+ const conversations = await twilioClient.conversations.v1.conversations.list({
78
+ limit: filters.limit || 50,
79
+ });
80
+ return conversations;
81
+ } catch (error) {
82
+ fastify.log.error("Conversations list failed:", error);
83
+ throw new Error("Failed to list conversations.");
84
+ }
85
+ },
86
+
87
+ /**
88
+ * Delete a conversation
89
+ * @param {string} conversationSid - Conversation SID
90
+ * @returns {Promise<boolean>} Success status
91
+ */
92
+ delete: async (conversationSid) => {
93
+ try {
94
+ await twilioClient.conversations.v1.conversations(conversationSid).remove();
95
+ return true;
96
+ } catch (error) {
97
+ fastify.log.error("Conversations delete failed:", error);
98
+ throw new Error("Failed to delete conversation.");
99
+ }
100
+ },
101
+
102
+ /**
103
+ * Add a participant to a conversation
104
+ * @param {string} conversationSid - Conversation SID
105
+ * @param {string} identity - Participant identity (for chat users)
106
+ * @param {string} messagingBindingAddress - Phone number (for SMS participants, e.g., "+1234567890")
107
+ * @returns {Promise<object>} Participant object
108
+ */
109
+ addParticipant: async (conversationSid, identity = null, messagingBindingAddress = null) => {
110
+ try {
111
+ const participantData = {};
112
+
113
+ if (identity) {
114
+ participantData.identity = identity;
115
+ }
116
+
117
+ if (messagingBindingAddress) {
118
+ participantData["messagingBinding.address"] = messagingBindingAddress;
119
+ participantData["messagingBinding.proxyAddress"] = options.phoneNumber || options.messagingServiceSid;
120
+ }
121
+
122
+ const participant = await twilioClient.conversations.v1
123
+ .conversations(conversationSid)
124
+ .participants.create(participantData);
125
+
126
+ return participant;
127
+ } catch (error) {
128
+ fastify.log.error("Conversations addParticipant failed:", error);
129
+ throw new Error("Failed to add participant.");
130
+ }
131
+ },
132
+
133
+ /**
134
+ * List participants in a conversation
135
+ * @param {string} conversationSid - Conversation SID
136
+ * @returns {Promise<object[]>} List of participants
137
+ */
138
+ listParticipants: async (conversationSid) => {
139
+ try {
140
+ const participants = await twilioClient.conversations.v1
141
+ .conversations(conversationSid)
142
+ .participants.list();
143
+ return participants;
144
+ } catch (error) {
145
+ fastify.log.error("Conversations listParticipants failed:", error);
146
+ throw new Error("Failed to list participants.");
147
+ }
148
+ },
149
+
150
+ /**
151
+ * Remove a participant from a conversation
152
+ * @param {string} conversationSid - Conversation SID
153
+ * @param {string} participantSid - Participant SID
154
+ * @returns {Promise<boolean>} Success status
155
+ */
156
+ removeParticipant: async (conversationSid, participantSid) => {
157
+ try {
158
+ await twilioClient.conversations.v1
159
+ .conversations(conversationSid)
160
+ .participants(participantSid)
161
+ .remove();
162
+ return true;
163
+ } catch (error) {
164
+ fastify.log.error("Conversations removeParticipant failed:", error);
165
+ throw new Error("Failed to remove participant.");
166
+ }
167
+ },
168
+
169
+ /**
170
+ * Send a message to a conversation
171
+ * @param {string} conversationSid - Conversation SID
172
+ * @param {string} body - Message content
173
+ * @param {string} author - Message author (optional, identity or participant SID)
174
+ * @param {object} attributes - Optional custom attributes
175
+ * @returns {Promise<object>} Message object
176
+ */
177
+ sendMessage: async (conversationSid, body, author = null, attributes = {}) => {
178
+ try {
179
+ const messageData = { body };
180
+
181
+ if (author) {
182
+ messageData.author = author;
183
+ }
184
+
185
+ if (Object.keys(attributes).length > 0) {
186
+ messageData.attributes = JSON.stringify(attributes);
187
+ }
188
+
189
+ const message = await twilioClient.conversations.v1
190
+ .conversations(conversationSid)
191
+ .messages.create(messageData);
192
+
193
+ return message;
194
+ } catch (error) {
195
+ fastify.log.error("Conversations sendMessage failed:", error);
196
+ throw new Error("Failed to send message.");
197
+ }
198
+ },
199
+
200
+ /**
201
+ * Send a media message to a conversation
202
+ * @param {string} conversationSid - Conversation SID
203
+ * @param {string} mediaUrl - URL of the media file
204
+ * @param {string} body - Optional message body
205
+ * @param {string} author - Message author (optional)
206
+ * @returns {Promise<object>} Message object
207
+ */
208
+ sendMediaMessage: async (conversationSid, mediaUrl, body = "", author = null) => {
209
+ try {
210
+ const messageData = {
211
+ body,
212
+ mediaUrl: [mediaUrl],
213
+ };
214
+
215
+ if (author) {
216
+ messageData.author = author;
217
+ }
218
+
219
+ const message = await twilioClient.conversations.v1
220
+ .conversations(conversationSid)
221
+ .messages.create(messageData);
222
+
223
+ return message;
224
+ } catch (error) {
225
+ fastify.log.error("Conversations sendMediaMessage failed:", error);
226
+ throw new Error("Failed to send media message.");
227
+ }
228
+ },
229
+
230
+ /**
231
+ * Get messages from a conversation
232
+ * @param {string} conversationSid - Conversation SID
233
+ * @param {object} options - Pagination options (limit, order)
234
+ * @returns {Promise<object[]>} List of messages
235
+ */
236
+ getMessages: async (conversationSid, options = {}) => {
237
+ try {
238
+ const messages = await twilioClient.conversations.v1
239
+ .conversations(conversationSid)
240
+ .messages.list({
241
+ limit: options.limit || 50,
242
+ order: options.order || "desc",
243
+ });
244
+ return messages;
245
+ } catch (error) {
246
+ fastify.log.error("Conversations getMessages failed:", error);
247
+ throw new Error("Failed to get messages.");
248
+ }
249
+ },
250
+
251
+ /**
252
+ * Get a specific message
253
+ * @param {string} conversationSid - Conversation SID
254
+ * @param {string} messageSid - Message SID
255
+ * @returns {Promise<object>} Message object
256
+ */
257
+ getMessage: async (conversationSid, messageSid) => {
258
+ try {
259
+ const message = await twilioClient.conversations.v1
260
+ .conversations(conversationSid)
261
+ .messages(messageSid)
262
+ .fetch();
263
+ return message;
264
+ } catch (error) {
265
+ fastify.log.error("Conversations getMessage failed:", error);
266
+ throw new Error("Failed to get message.");
267
+ }
268
+ },
269
+
270
+ /**
271
+ * Delete a message from a conversation
272
+ * @param {string} conversationSid - Conversation SID
273
+ * @param {string} messageSid - Message SID
274
+ * @returns {Promise<boolean>} Success status
275
+ */
276
+ deleteMessage: async (conversationSid, messageSid) => {
277
+ try {
278
+ await twilioClient.conversations.v1
279
+ .conversations(conversationSid)
280
+ .messages(messageSid)
281
+ .remove();
282
+ return true;
283
+ } catch (error) {
284
+ fastify.log.error("Conversations deleteMessage failed:", error);
285
+ throw new Error("Failed to delete message.");
286
+ }
287
+ },
288
+
289
+ /**
290
+ * Get conversation webhooks configuration
291
+ * @param {string} conversationSid - Conversation SID
292
+ * @returns {Promise<object>} Webhooks configuration
293
+ */
294
+ getWebhooks: async (conversationSid) => {
295
+ try {
296
+ const webhooks = await twilioClient.conversations.v1
297
+ .conversations(conversationSid)
298
+ .webhooks()
299
+ .fetch();
300
+ return webhooks;
301
+ } catch (error) {
302
+ fastify.log.error("Conversations getWebhooks failed:", error);
303
+ throw new Error("Failed to get webhooks.");
304
+ }
305
+ },
306
+
307
+ /**
308
+ * Update conversation webhooks
309
+ * @param {string} conversationSid - Conversation SID
310
+ * @param {object} webhookConfig - Webhook configuration
311
+ * @returns {Promise<object>} Updated webhooks configuration
312
+ */
313
+ updateWebhooks: async (conversationSid, webhookConfig) => {
314
+ try {
315
+ const webhooks = await twilioClient.conversations.v1
316
+ .conversations(conversationSid)
317
+ .webhooks()
318
+ .update(webhookConfig);
319
+ return webhooks;
320
+ } catch (error) {
321
+ fastify.log.error("Conversations updateWebhooks failed:", error);
322
+ throw new Error("Failed to update webhooks.");
323
+ }
324
+ },
325
+ });
326
+
327
+ console.info(" ✅ Conversations Service Enabled");
328
+ }