@pipedream/twilio 0.3.3 → 0.3.5

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 CHANGED
@@ -1,20 +1,21 @@
1
1
  {
2
- "name": "@pipedream/twilio",
3
- "version": "0.3.3",
4
- "description": "Pipedream Twilio Components",
5
- "main": "twilio.app.js",
6
- "keywords": [
7
- "pipedream",
8
- "twilio"
9
- ],
10
- "homepage": "https://pipedream.com/apps/twilio",
11
- "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
12
- "license": "MIT",
13
- "dependencies": {
14
- "twilio": "^3.54.2"
15
- },
16
- "gitHead": "12ad36100ee95629c1b22923c79923bfff156ca7",
17
- "publishConfig": {
18
- "access": "public"
19
- }
20
- }
2
+ "name": "@pipedream/twilio",
3
+ "version": "0.3.5",
4
+ "description": "Pipedream Twilio Components",
5
+ "main": "twilio.app.mjs",
6
+ "keywords": [
7
+ "pipedream",
8
+ "twilio"
9
+ ],
10
+ "homepage": "https://pipedream.com/apps/twilio",
11
+ "author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "phone": "^3.1.29",
15
+ "twilio": "^3.54.2"
16
+ },
17
+ "gitHead": "e12480b94cc03bed4808ebc6b13e7fdb3a1ba535",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ }
21
+ }
@@ -0,0 +1,44 @@
1
+ import twilio from "../twilio.app.mjs";
2
+
3
+ export default {
4
+ props: {
5
+ twilio,
6
+ db: "$.service.db",
7
+ timer: {
8
+ label: "Polling schedule",
9
+ description: "Pipedream polls Twilio for events on this schedule.",
10
+ type: "$.interface.timer",
11
+ default: {
12
+ intervalSeconds: 60 * 15,
13
+ },
14
+ },
15
+ },
16
+ methods: {
17
+ _getCreatedAfter() {
18
+ return this.db.get("createdAfter");
19
+ },
20
+ _setCreatedAfter(createdAfter) {
21
+ this.db.set("createdAfter", createdAfter);
22
+ },
23
+ emitEvent(result) {
24
+ const meta = this.generateMeta(result);
25
+ this.$emit(result, meta);
26
+ },
27
+ },
28
+ async run() {
29
+ let dateCreatedAfter = this._getCreatedAfter();
30
+ const params = {
31
+ dateCreatedAfter,
32
+ };
33
+ const results = await this.listResults(params);
34
+ for (const result of results) {
35
+ this.emitEvent(result);
36
+ if (
37
+ !dateCreatedAfter ||
38
+ Date.parse(result.dateCreated) > Date.parse(dateCreatedAfter)
39
+ )
40
+ dateCreatedAfter = result.dateCreated;
41
+ }
42
+ this._setCreatedAfter(dateCreatedAfter);
43
+ },
44
+ };
@@ -0,0 +1,99 @@
1
+ import twilio from "../twilio.app.mjs";
2
+
3
+ export default {
4
+ props: {
5
+ twilio,
6
+ incomingPhoneNumber: {
7
+ propDefinition: [
8
+ twilio,
9
+ "incomingPhoneNumber",
10
+ ],
11
+ },
12
+ http: {
13
+ label: "HTTP Responder",
14
+ description: "Exposes a `respond()` method that lets the source issue HTTP responses",
15
+ type: "$.interface.http",
16
+ customResponse: true,
17
+ },
18
+ },
19
+ hooks: {
20
+ async activate() {
21
+ const createWebhookResp = await this.twilio.setWebhookURL({
22
+ serviceType: this.getServiceType(),
23
+ phoneNumberSid: this.incomingPhoneNumber,
24
+ url: this.http.endpoint,
25
+ });
26
+ console.log(createWebhookResp);
27
+ },
28
+ async deactivate() {
29
+ // remove the webhook URL if url prop is not set
30
+ const deleteWebhookResp = await this.twilio.setWebhookURL({
31
+ serviceType: this.getServiceType(),
32
+ phoneNumberSid: this.incomingPhoneNumber,
33
+ url: "",
34
+ });
35
+ console.log(deleteWebhookResp);
36
+ },
37
+ },
38
+ methods: {
39
+ getServiceType() {
40
+ throw new Error("getServiceType() is not implemented!");
41
+ },
42
+ getResponseBody() {
43
+ return null;
44
+ },
45
+ isRelevant() {
46
+ return true;
47
+ },
48
+ emitEvent(body, headers) {
49
+ const meta = this.generateMeta(body, headers);
50
+ this.$emit(body, meta);
51
+ },
52
+ },
53
+ async run(event) {
54
+ let {
55
+ body,
56
+ headers,
57
+ } = event;
58
+
59
+ const responseBody = this.getResponseBody();
60
+ if (responseBody) {
61
+ this.http.respond({
62
+ status: 200,
63
+ headers: {
64
+ "Content-Type": "text/xml",
65
+ },
66
+ body: responseBody,
67
+ });
68
+ }
69
+
70
+ if (typeof body !== "object") {
71
+ body = Object.fromEntries(new URLSearchParams(body));
72
+ }
73
+
74
+ if (!this.isRelevant(body)) {
75
+ console.log("Event not relevant. Skipping...");
76
+ return;
77
+ }
78
+
79
+ const signature = headers["x-twilio-signature"];
80
+ if (!signature) {
81
+ console.log("No x-twilio-signature header in request. Exiting.");
82
+ return;
83
+ }
84
+
85
+ // The url must match the incoming URL exactly, which contains a `/` at the end
86
+ const isRequestValid = this.twilio.validateRequest({
87
+ signature,
88
+ url: `${this.http.endpoint}/`,
89
+ params: body,
90
+ });
91
+
92
+ if (!isRequestValid) {
93
+ console.log("Event could not be validated. Skipping...");
94
+ return;
95
+ }
96
+
97
+ this.emitEvent(body, headers);
98
+ },
99
+ };
@@ -0,0 +1,33 @@
1
+ import common from "../common-webhook.mjs";
2
+ import constants from "../../common/constants.mjs";
3
+
4
+ export default {
5
+ ...common,
6
+ key: "twilio-new-call",
7
+ name: "New Call (Instant)",
8
+ description: "Emit new event each time a call to the phone number is completed. Configures a webhook in Twilio, tied to a phone number.",
9
+ version: "0.1.0",
10
+ type: "source",
11
+ dedupe: "unique",
12
+ methods: {
13
+ ...common.methods,
14
+ getServiceType() {
15
+ return constants.SERVICE_TYPE.VOICE;
16
+ },
17
+ generateMeta(body, headers) {
18
+ return {
19
+ /** if Twilio retries a message, but we've already emitted, dedupe */
20
+ id: headers["i-twilio-idempotency-token"],
21
+ summary: `New call from ${this.getMaskedNumber(body.From)}`,
22
+ ts: Date.now(),
23
+ };
24
+ },
25
+ isRelevant(body) {
26
+ return body.CallStatus == "completed";
27
+ },
28
+ getMaskedNumber(number) {
29
+ const { length: numberLength } = number;
30
+ return number.slice(numberLength - 4).padStart(numberLength, "#");
31
+ },
32
+ },
33
+ };
@@ -0,0 +1,47 @@
1
+ import twilio from "twilio";
2
+ import common from "../common-webhook.mjs";
3
+ import constants from "../../common/constants.mjs";
4
+
5
+ const MessagingResponse = twilio.twiml.MessagingResponse;
6
+
7
+ export default {
8
+ ...common,
9
+ key: "twilio-new-incoming-sms",
10
+ name: "New Incoming SMS (Instant)",
11
+ description: "Emit new event every time an SMS is sent to the phone number set. Configures a webhook in Twilio, tied to an incoming phone number.",
12
+ version: "0.1.0",
13
+ type: "source",
14
+ dedupe: "unique",
15
+ props: {
16
+ ...common.props,
17
+ responseMessage: {
18
+ propDefinition: [
19
+ common.props.twilio,
20
+ "responseMessage",
21
+ ],
22
+ },
23
+ },
24
+ methods: {
25
+ ...common.methods,
26
+ getServiceType() {
27
+ return constants.SERVICE_TYPE.SMS;
28
+ },
29
+ getResponseBody() {
30
+ const twiml = new MessagingResponse();
31
+ let responseBody = "<Response></Response>";
32
+ if (this.responseMessage) {
33
+ twiml.message(this.responseMessage);
34
+ responseBody = twiml.toString();
35
+ }
36
+ return responseBody;
37
+ },
38
+ generateMeta(body, headers) {
39
+ return {
40
+ /** if Twilio retries a message, but we've already emitted, dedupe */
41
+ id: headers["i-twilio-idempotency-token"],
42
+ summary: body.Body,
43
+ ts: Date.now(),
44
+ };
45
+ },
46
+ },
47
+ };
@@ -0,0 +1,29 @@
1
+ import common from "../common-polling.mjs";
2
+
3
+ export default {
4
+ ...common,
5
+ key: "twilio-new-phone-number",
6
+ name: "New Phone Number",
7
+ description: "Emit new event when you add a new phone number to your account",
8
+ version: "0.1.0",
9
+ type: "source",
10
+ dedupe: "unique",
11
+ methods: {
12
+ ...common.methods,
13
+ async listResults(...args) {
14
+ return await this.twilio.listIncomingPhoneNumbers(...args);
15
+ },
16
+ generateMeta(number) {
17
+ const {
18
+ sid: id,
19
+ friendlyName: summary,
20
+ dateCreated,
21
+ } = number;
22
+ return {
23
+ id,
24
+ summary,
25
+ ts: Date.parse(dateCreated),
26
+ };
27
+ },
28
+ },
29
+ };
@@ -0,0 +1,28 @@
1
+ import common from "../common-polling.mjs";
2
+
3
+ export default {
4
+ ...common,
5
+ key: "twilio-new-recording",
6
+ name: "New Recording",
7
+ description: "Emit new event when a new call recording is created",
8
+ version: "0.1.0",
9
+ type: "source",
10
+ dedupe: "unique",
11
+ methods: {
12
+ ...common.methods,
13
+ async listResults(...args) {
14
+ return await this.twilio.listRecordings(...args);
15
+ },
16
+ generateMeta(recording) {
17
+ const {
18
+ sid: id,
19
+ dateCreated,
20
+ } = recording;
21
+ return {
22
+ id,
23
+ summary: `New recording ${id}`,
24
+ ts: Date.parse(dateCreated),
25
+ };
26
+ },
27
+ },
28
+ };
@@ -0,0 +1,28 @@
1
+ import common from "../common-polling.mjs";
2
+
3
+ export default {
4
+ ...common,
5
+ key: "twilio-new-transcription",
6
+ name: "New Transcription",
7
+ description: "Emit new event when a new call transcription is created",
8
+ version: "0.1.0",
9
+ type: "source",
10
+ dedupe: "unique",
11
+ methods: {
12
+ ...common.methods,
13
+ async listResults(...args) {
14
+ return await this.twilio.listTranscriptions(...args);
15
+ },
16
+ generateMeta(transcription) {
17
+ const {
18
+ sid: id,
19
+ dateCreated,
20
+ } = transcription;
21
+ return {
22
+ id,
23
+ summary: `New transcription ${id}`,
24
+ ts: Date.parse(dateCreated),
25
+ };
26
+ },
27
+ },
28
+ };
package/twilio.app.mjs ADDED
@@ -0,0 +1,338 @@
1
+ import twilio from "twilio";
2
+ import {
3
+ callToString,
4
+ messageToString,
5
+ recordingToString,
6
+ } from "./common/utils.mjs";
7
+ import constants from "./common/constants.mjs";
8
+
9
+ export default {
10
+ type: "app",
11
+ app: "twilio",
12
+ propDefinitions: {
13
+ body: {
14
+ type: "string",
15
+ label: "Message Body",
16
+ description: "The text of the message you want to send, limited to 1600 characters.",
17
+ },
18
+ from: {
19
+ type: "string",
20
+ label: "From",
21
+ description: "The phone number or alphanumeric sender ID the message is from",
22
+ async options() {
23
+ const numbers = await this.listIncomingPhoneNumbers();
24
+ return numbers.map((number) => {
25
+ return {
26
+ label: number.friendlyName,
27
+ value: number.phoneNumber,
28
+ };
29
+ });
30
+ },
31
+ },
32
+ incomingPhoneNumber: {
33
+ type: "string",
34
+ label: "Incoming Phone Number",
35
+ description:
36
+ "The Twilio phone number where you'll receive messages. This source creates a webhook tied to this incoming phone number, **overwriting any existing webhook URL**.",
37
+ async options() {
38
+ const numbers = await this.listIncomingPhoneNumbers();
39
+ return numbers.map((number) => {
40
+ return {
41
+ label: number.friendlyName,
42
+ value: number.sid,
43
+ };
44
+ });
45
+ },
46
+ },
47
+ mediaUrl: {
48
+ type: "string[]",
49
+ label: "Media URL",
50
+ description: "The URL of the media you wish to send out with the message. The media size limit is `5MB`. You may provide up to 10 media URLs per message.",
51
+ optional: true,
52
+ },
53
+ responseMessage: {
54
+ type: "string",
55
+ optional: true,
56
+ label: "Response Message",
57
+ description:
58
+ "The message you want to send in response to incoming messages. Leave this blank if you don't need to issue a response.",
59
+ },
60
+ to: {
61
+ type: "string",
62
+ label: "To",
63
+ description: "The destination phone number in E.164 format. Format with a `+` and country code (e.g., `+16175551212`).",
64
+ },
65
+ limit: {
66
+ type: "integer",
67
+ label: "Limit",
68
+ description: "The maximum number of results to be worked with during one execution cycle.",
69
+ optional: true,
70
+ default: 50,
71
+ },
72
+ messageId: {
73
+ type: "string",
74
+ label: "Message ID",
75
+ description: "The SID of the Message",
76
+ optional: true,
77
+ async options() {
78
+ const messages = await this.listMessages();
79
+ return messages.map((message) => {
80
+ return {
81
+ label: messageToString(message),
82
+ value: message.sid,
83
+ };
84
+ });
85
+ },
86
+ },
87
+ parentCallSid: {
88
+ type: "string",
89
+ label: "Parent Call SID",
90
+ description: "Only include calls spawned by calls with this SID.",
91
+ optional: true,
92
+ async options() {
93
+ return this.listCallsOptions();
94
+ },
95
+ },
96
+ sid: {
97
+ type: "string",
98
+ label: "Call ID",
99
+ description: "The SID of the Call",
100
+ optional: true,
101
+ async options() {
102
+ return this.listCallsOptions();
103
+ },
104
+ },
105
+ recordingID: {
106
+ type: "string",
107
+ label: "Recording ID",
108
+ description: "The SID of the Recording",
109
+ async options() {
110
+ const recordings = await this.listRecordings();
111
+ return recordings.map((recording) => {
112
+ return {
113
+ label: recordingToString(recording),
114
+ value: recording.sid,
115
+ };});
116
+ },
117
+ },
118
+ format: {
119
+ type: "string",
120
+ label: "Format",
121
+ description: "The format of the recording audio file",
122
+ options: [
123
+ {
124
+ label: "MP3",
125
+ value: ".mp3",
126
+ },
127
+ {
128
+ label: "WAV",
129
+ value: ".wav",
130
+ },
131
+ ],
132
+ default: ".wav",
133
+ },
134
+ status: {
135
+ type: "string",
136
+ label: "Status",
137
+ description: "The status of the call",
138
+ optional: true,
139
+ options: [
140
+ "queued",
141
+ "ringing",
142
+ "in-progress",
143
+ "canceled",
144
+ "completed",
145
+ "failed",
146
+ "busy",
147
+ "no-answer",
148
+ ],
149
+ },
150
+ },
151
+ methods: {
152
+ validateRequest({
153
+ signature, url, params,
154
+ } = {}) {
155
+ // See https://www.twilio.com/docs/usage/webhooks/webhooks-security
156
+ return twilio.validateRequest(
157
+ this.$auth.authToken,
158
+ signature,
159
+ url,
160
+ params,
161
+ );
162
+ },
163
+ getClient() {
164
+ return twilio(this.$auth.accountSid, this.$auth.authToken);
165
+ },
166
+ setWebhookURL({
167
+ serviceType = constants.SERVICE_TYPE.SMS, phoneNumberSid, url,
168
+ } = {}) {
169
+ const opts = serviceType === constants.SERVICE_TYPE.SMS
170
+ ? {
171
+ smsMethod: constants.HTTP_METHOD.POST,
172
+ smsUrl: url,
173
+ }
174
+ : {
175
+ statusCallbackMethod: constants.HTTP_METHOD.POST,
176
+ statusCallback: url,
177
+ };
178
+ const client = this.getClient();
179
+ return client.incomingPhoneNumbers(phoneNumberSid).update(opts);
180
+ },
181
+ /**
182
+ * Returns a IncomingPhoneNumber list resource representing an account's
183
+ * Twilio phone numbers
184
+ *
185
+ * @param {object} [params] - an object containing parameters to be fed to
186
+ * the Twilio API call, as defined in [the API docs](https://bit.ly/3F8y6KY)
187
+ *
188
+ * @returns the IncomingPhoneNumber list
189
+ */
190
+ listIncomingPhoneNumbers(params) {
191
+ const client = this.getClient();
192
+ return client.incomingPhoneNumbers.list(params);
193
+ },
194
+ /**
195
+ * Returns a list of Recordings, each representing a recording generated
196
+ * during a call or conference for the given account
197
+ *
198
+ * @param {object} [params] - an object containing parameters to be fed to
199
+ * the Twilio API call, as defined in [the API docs](https://bit.ly/3mg9ebJ)
200
+ *
201
+ * @returns the list of recordings
202
+ */
203
+ listRecordings(params) {
204
+ const client = this.getClient();
205
+ return client.recordings.list(params);
206
+ },
207
+ /**
208
+ * Returns a list of Transcriptions generated from all recordings in an
209
+ * account
210
+ *
211
+ * @param {object} [params] - an object containing parameters to be fed to
212
+ * the Twilio API call, as defined in [the API docs](https://bit.ly/3uwx5HB)
213
+ *
214
+ * @returns the list of transcriptions
215
+ */
216
+ listTranscriptions(params) {
217
+ const client = this.getClient();
218
+ return client.transcriptions.list(params);
219
+ },
220
+ /**
221
+ * Returns a list of messages associated with your account. When getting the
222
+ * list of all messages, results will be sorted on the DateSent field with
223
+ * the most recent messages appearing first.
224
+ *
225
+ * @param {object} [params] - an object containing parameters to be fed to the
226
+ * Twilio API call, as defined in [the API docs](https://bit.ly/3mh26eY)
227
+ *
228
+ * @returns the list of messages
229
+ */
230
+ listMessages(params) {
231
+ const client = this.getClient();
232
+ return client.messages.list(params);
233
+ },
234
+ /**
235
+ * Returns a single message specified by the provided Message `sid`
236
+ *
237
+ * @param {String} sid - the Twilio-provided string that uniquely identifies
238
+ * the Message resource to fetch
239
+ *
240
+ * @returns the message
241
+ */
242
+ getMessage(sid) {
243
+ const client = this.getClient();
244
+ return client.messages(sid).fetch();
245
+ },
246
+ /**
247
+ * Deletes a message record from your account. Once the record is deleted,
248
+ * it will no longer appear in the API and Account Portal logs.
249
+ *
250
+ * @param {String} sid - the Twilio-provided string that uniquely identifies
251
+ * the Message resource to delete
252
+ * @returns `true` on success
253
+ */
254
+ deleteMessage(sid) {
255
+ const client = this.getClient();
256
+ return client.messages(sid).remove();
257
+ },
258
+ /**
259
+ * Returns a list of media associated with your message
260
+ *
261
+ * @param {String} messageId - The SID of the Message resource that this
262
+ * Media resource belongs to
263
+ * @param {Object} [params] - an object containing parameters to be fed to the
264
+ * Twilio API call, as defined in [the API docs](https://bit.ly/3mc8apg)
265
+ */
266
+ listMessageMedia(messageId, params) {
267
+ const client = this.getClient();
268
+ return client.messages(messageId).media.list(params);
269
+ },
270
+ /**
271
+ * Return a list of phone calls made to and from an account
272
+ *
273
+ * @param {Object} params - an object containing parameters to be fed to the
274
+ * Twilio API call, as defined in [the API docs](https://bit.ly/3iiHIJ9)
275
+ * @returns the list of calls
276
+ */
277
+ listCalls(params) {
278
+ const client = this.getClient();
279
+ return client.calls.list(params);
280
+ },
281
+ async listCallsOptions(params = {}) {
282
+ const calls = await this.listCalls(params);
283
+ return calls.map((call) => {
284
+ return {
285
+ label: callToString(call),
286
+ value: call.sid,
287
+ };
288
+ });
289
+ },
290
+ /**
291
+ * Returns the Call resource of an individual call, identified by its Call `sid`
292
+ *
293
+ * @param {String} sid - The SID of the Call resource to fetch
294
+ * @returns the Call
295
+ */
296
+ getCall(sid) {
297
+ const client = this.getClient();
298
+ return client.calls(sid).fetch();
299
+ },
300
+ /**
301
+ * This will delete a call record from the account. Once the record is
302
+ * deleted, it will no longer appear in the API and Account Portal logs.
303
+ *
304
+ * @param {String} sid - the Twilio-provided Call SID that uniquely identifies
305
+ * the Call resource to delete
306
+ * @returns `true` on success
307
+ */
308
+ deleteCall(sid) {
309
+ const client = this.getClient();
310
+ return client.calls(sid).remove();
311
+ },
312
+ /**
313
+ * Returns a recording specified by the provided Recording `sid`
314
+ *
315
+ * @param {String} sid - the Twilio-provided string that uniquely identifies
316
+ * the Recording resource to fetch
317
+ *
318
+ * @returns the recording
319
+ */
320
+ getRecording(sid) {
321
+ const client = this.getClient();
322
+ return client.recordings(sid).fetch();
323
+ },
324
+ /**
325
+ * Returns a list of transcriptions available for the recording identified
326
+ * by the Recording `sid`
327
+ *
328
+ * @param {String} sid - the Twilio-provided string that uniquely identifies
329
+ * the Recording resource
330
+ *
331
+ * @returns the list of transcriptions
332
+ */
333
+ listRecordingTranscriptions(sid, params) {
334
+ const client = this.getClient();
335
+ return client.recordings(sid).transcriptions.list(params);
336
+ },
337
+ },
338
+ };