From ac296416a699141bde024a632d565b8430844ccf Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 4 Nov 2024 15:25:07 -0300 Subject: [PATCH 1/4] openphone init --- .../actions/create-contact/create-contact.mjs | 70 +++++++ .../create-update-contact.mjs | 83 ++++++++ .../actions/send-message/send-message.mjs | 56 +++++ components/openphone/openphone.app.mjs | 197 +++++++++++++++++- components/openphone/package.json | 2 +- .../new-call-recording-completed-instant.mjs | 100 +++++++++ .../new-incoming-call-completed.mjs | 125 +++++++++++ .../new-outgoing-call-completed.mjs | 58 ++++++ 8 files changed, 685 insertions(+), 6 deletions(-) create mode 100644 components/openphone/actions/create-contact/create-contact.mjs create mode 100644 components/openphone/actions/create-update-contact/create-update-contact.mjs create mode 100644 components/openphone/actions/send-message/send-message.mjs create mode 100644 components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs create mode 100644 components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs create mode 100644 components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs diff --git a/components/openphone/actions/create-contact/create-contact.mjs b/components/openphone/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..77021f0482ff3 --- /dev/null +++ b/components/openphone/actions/create-contact/create-contact.mjs @@ -0,0 +1,70 @@ +import openphone from "../../openphone.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "openphone-create-contact", + name: "Create Contact", + description: "Create a new contact in OpenPhone. [See the documentation](https://www.openphone.com/docs/api-reference/contacts/create-a-contact)", + version: "0.0.1", + type: "action", + props: { + openphone, + firstName: { + type: "string", + label: "First Name", + description: "The contact's first name.", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The contact's last name.", + optional: true, + }, + company: { + type: "string", + label: "Company", + description: "The contact's company name.", + optional: true, + }, + role: { + type: "string", + label: "Role", + description: "The contact's role.", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "Array of contact's emails.", + optional: true, + }, + phoneNumbers: { + type: "string[]", + label: "Phone Numbers", + description: "Array of contact's phone numbers.", + optional: true, + }, + customFields: { + type: "string[]", + label: "Custom Fields", + description: "Array of custom fields for the contact.", + optional: true, + }, + }, + async run({ $ }) { + const data = { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: this.emails, + phoneNumbers: this.phoneNumbers, + customFields: this.customFields, + }; + + const response = await this.openphone.createContact(data); + + $.export("$summary", `Successfully created contact with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/openphone/actions/create-update-contact/create-update-contact.mjs b/components/openphone/actions/create-update-contact/create-update-contact.mjs new file mode 100644 index 0000000000000..b824634ddba37 --- /dev/null +++ b/components/openphone/actions/create-update-contact/create-update-contact.mjs @@ -0,0 +1,83 @@ +import openphone from "../../openphone.app.mjs"; + +export default { + key: "openphone-update-contact", + name: "Update Contact", + description: "Update an existing contact on OpenPhone. [See the documentation](https://www.openphone.com/docs/api-reference/contacts/update-a-contact-by-id)", + version: "0.0.{{ts}}", + type: "action", + props: { + openphone, + contactId: { + propDefinition: [ + openphone, + "contactId", + ], + }, + firstName: { + propDefinition: [ + openphone, + "firstName", + ], + optional: true, + }, + lastName: { + propDefinition: [ + openphone, + "lastName", + ], + optional: true, + }, + company: { + propDefinition: [ + openphone, + "company", + ], + optional: true, + }, + role: { + propDefinition: [ + openphone, + "role", + ], + optional: true, + }, + emails: { + propDefinition: [ + openphone, + "emails", + ], + optional: true, + }, + phoneNumbers: { + propDefinition: [ + openphone, + "phoneNumbers", + ], + optional: true, + }, + customFields: { + propDefinition: [ + openphone, + "customFields", + ], + optional: true, + }, + }, + async run({ $ }) { + const data = { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: this.emails, + phoneNumbers: this.phoneNumbers, + customFields: this.customFields, + }; + + const response = await this.openphone.updateContact(this.contactId, data); + + $.export("$summary", `Successfully updated contact with ID ${this.contactId}`); + return response; + }, +}; diff --git a/components/openphone/actions/send-message/send-message.mjs b/components/openphone/actions/send-message/send-message.mjs new file mode 100644 index 0000000000000..c1bacee4d6362 --- /dev/null +++ b/components/openphone/actions/send-message/send-message.mjs @@ -0,0 +1,56 @@ +import openphone from "../../openphone.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "openphone-send-message", + name: "Send a Text Message via OpenPhone", + description: "Send a text message from your OpenPhone number to a recipient. [See the documentation](https://www.openphone.com/docs/api-reference/messages/send-a-text-message)", + version: "0.0.{{ts}}", + type: "action", + props: { + openphone, + from: { + propDefinition: [ + openphone, + "from", + ], + }, + to: { + propDefinition: [ + openphone, + "to", + ], + }, + content: { + propDefinition: [ + openphone, + "content", + ], + }, + userId: { + propDefinition: [ + openphone, + "userId", + ], + optional: true, + }, + setInboxStatus: { + propDefinition: [ + openphone, + "setInboxStatus", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.openphone.sendTextMessage({ + from: this.from, + to: this.to, + content: this.content, + userId: this.userId, + setInboxStatus: this.setInboxStatus, + }); + $.export("$summary", `Successfully sent message to ${this.to}`); + return response; + }, +}; diff --git a/components/openphone/openphone.app.mjs b/components/openphone/openphone.app.mjs index fc77209e14dbc..4431c38522808 100644 --- a/components/openphone/openphone.app.mjs +++ b/components/openphone/openphone.app.mjs @@ -1,11 +1,198 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "openphone", - propDefinitions: {}, + propDefinitions: { + resourceIds: { + type: "string[]", + label: "Resource IDs", + description: "The unique identifiers of phone numbers associated with the webhook.", + optional: true, + async options() { + const phoneNumbers = await this.listPhoneNumbers(); + return phoneNumbers.map((phone) => ({ + label: phone.formattedNumber, + value: phone.id, + })); + }, + }, + label: { + type: "string", + label: "Label", + description: "Webhook's label", + optional: true, + }, + from: { + type: "string", + label: "From", + description: "The sender's phone number. Can be either your OpenPhone phone number ID or the full phone number in E.164 format.", + async options() { + const phoneNumbers = await this.listPhoneNumbers(); + return phoneNumbers.map((phone) => ({ + label: phone.formattedNumber, + value: phone.id, + })); + }, + }, + to: { + type: "string[]", + label: "To", + description: "Array of recipient phone numbers in E.164 format. Currently only supports one recipient.", + }, + content: { + type: "string", + label: "Content", + description: "The text content of the message to be sent.", + }, + userId: { + type: "string", + label: "User ID", + description: "The unique identifier of the OpenPhone user sending the message.", + optional: true, + }, + setInboxStatus: { + type: "string", + label: "Inbox Status", + description: "Set the status of the related OpenPhone inbox conversation.", + optional: true, + options: [ + { + label: "Done", + value: "done", + }, + ], + }, + firstName: { + type: "string", + label: "First Name", + description: "The contact's first name.", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The contact's last name.", + optional: true, + }, + company: { + type: "string", + label: "Company", + description: "The contact's company name.", + optional: true, + }, + role: { + type: "string", + label: "Role", + description: "The contact's role.", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "Array of contact's emails.", + optional: true, + }, + phoneNumbers: { + type: "string[]", + label: "Phone Numbers", + description: "Array of contact's phone numbers.", + optional: true, + }, + customFields: { + type: "string[]", + label: "Custom Fields", + description: "Array of custom fields for the contact.", + optional: true, + }, + contactId: { + type: "string", + label: "Contact ID", + description: "The unique identifier of the contact.", + }, + recording: { + type: "boolean", + label: "Voicemail Recording", + description: "Include voicemail recording in the response.", + }, + transcription: { + type: "boolean", + label: "Voicemail Transcription", + description: "Include voicemail transcription in the response.", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.openphone.com"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_key}`, + }, + }); + }, + async listPhoneNumbers(opts = {}) { + return this._makeRequest({ + path: "/v1/phone-numbers", + ...opts, + }); + }, + async createWebhook(data) { + return this._makeRequest({ + method: "POST", + path: "/v1/webhooks/calls", + data, + }); + }, + async sendTextMessage(data) { + return this._makeRequest({ + method: "POST", + path: "/v1/messages", + data: { + from: this.from, + to: this.to, + content: this.content, + userId: this.userId, + setInboxStatus: this.setInboxStatus, + }, + }); + }, + async createContact(data) { + return this._makeRequest({ + method: "POST", + path: "/v1/contacts", + data: { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: this.emails, + phoneNumbers: this.phoneNumbers, + customFields: this.customFields, + }, + }); + }, + async updateContact(contactId, data) { + return this._makeRequest({ + method: "PATCH", + path: `/v1/contacts/${contactId}`, + data: { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: this.emails, + phoneNumbers: this.phoneNumbers, + customFields: this.customFields, + }, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/openphone/package.json b/components/openphone/package.json index a51350e9e59c9..62226a670a982 100644 --- a/components/openphone/package.json +++ b/components/openphone/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs new file mode 100644 index 0000000000000..b2ebf490b561f --- /dev/null +++ b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs @@ -0,0 +1,100 @@ +import openphone from "../../openphone.app.mjs"; +import crypto from "crypto"; +import { axios } from "@pipedream/platform"; + +export default { + key: "openphone-new-call-recording-completed-instant", + name: "New Call Recording Completed", + description: "Emit a new event when a call recording has finished. [See the documentation](https://www.openphone.com/docs/api-reference/webhooks/create-a-new-webhook-for-calls)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + openphone, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + resourceIds: { + propDefinition: [ + openphone, + "resourceIds", + ], + optional: true, + }, + label: { + propDefinition: [ + openphone, + "label", + ], + optional: true, + }, + }, + hooks: { + async deploy() { + const webhooks = await this.openphone.listWebhooks(); + for (const webhook of webhooks.slice(0, 50)) { + this.$emit(webhook, { + id: webhook.id, + summary: `Webhook ID: ${webhook.id}`, + ts: Date.parse(webhook.createdAt), + }); + } + }, + async activate() { + const response = await this.openphone.createWebhook({ + url: this.http.endpoint, + events: [ + "call.recording.completed", + ], + resourceIds: this.resourceIds, + label: this.label, + }); + this.setWebhookId(response.id); + }, + async deactivate() { + const webhookId = this.getWebhookId(); + if (webhookId) { + await this.openphone.deleteWebhook(webhookId); + this.setWebhookId(null); + } + }, + }, + methods: { + getWebhookId() { + return this.db.get("webhookId"); + }, + setWebhookId(id) { + this.db.set("webhookId", id); + }, + }, + async run(event) { + const rawBody = event.bodyRaw; + const secretKey = this.openphone.$auth.api_key; + const webhookSignature = event.headers["x-openphone-signature"]; + + const computedSignature = crypto.createHmac("sha256", secretKey).update(rawBody) + .digest("base64"); + + if (computedSignature !== webhookSignature) { + this.http.respond({ + status: 401, + body: "Unauthorized", + }); + return; + } + + this.http.respond({ + status: 200, + body: "OK", + }); + + const data = event.body; + this.$emit(data, { + id: data.id, + summary: `New call recording completed for call ID: ${data.callId}`, + ts: Date.parse(data.completedAt), + }); + }, +}; diff --git a/components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs b/components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs new file mode 100644 index 0000000000000..23c9f7a3eb710 --- /dev/null +++ b/components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs @@ -0,0 +1,125 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import openphone from "../../openphone.app.mjs"; + +export default { + key: "openphone-new-incoming-call-completed", + name: "New Incoming Call Completed", + description: "Emit new event when an incoming call is completed, including calls not picked up or voicemails left. [See the documentation](https://www.openphone.com/docs/api-reference/calls/list-calls)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + openphone, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + phoneNumberId: { + type: "string", + label: "Phone Number ID", + description: "The unique identifier of the OpenPhone number associated with the call.", + async options() { + const phoneNumbers = await this.openphone.listPhoneNumbers(); + return phoneNumbers.map((phone) => ({ + label: phone.formattedNumber, + value: phone.id, + })); + }, + }, + recording: { + propDefinition: [ + openphone, + "recording", + ], + }, + transcription: { + propDefinition: [ + openphone, + "transcription", + ], + }, + }, + methods: { + _getLastCreatedAt() { + return this.db.get("lastCreatedAt") || null; + }, + _setLastCreatedAt(createdAt) { + this.db.set("lastCreatedAt", createdAt); + }, + async _fetchCalls(params) { + return this.openphone._makeRequest({ + path: "/v1/calls", + params, + }); + }, + }, + hooks: { + async deploy() { + const params = { + phoneNumberId: this.phoneNumberId, + maxResults: 50, + }; + const calls = await this._fetchCalls(params); + + calls.forEach((call) => { + if ( + call.direction === "incoming" && + call.status === "completed" && + !call.answeredAt + ) { + this.$emit(call, { + id: call.id, + summary: `New Incoming Call Completed: ${call.participants[0]}`, + ts: Date.parse(call.createdAt), + }); + } + }); + + if (calls.length > 0) { + const lastCreatedAt = calls.reduce((max, call) => + Date.parse(call.createdAt) > max + ? Date.parse(call.createdAt) + : max, 0); + this._setLastCreatedAt(lastCreatedAt); + } + }, + }, + async run() { + const lastCreatedAt = this._getLastCreatedAt(); + const params = { + phoneNumberId: this.phoneNumberId, + createdAfter: lastCreatedAt + ? new Date(lastCreatedAt).toISOString() + : undefined, + maxResults: 50, + }; + const calls = await this._fetchCalls(params); + + calls.forEach((call) => { + if ( + call.direction === "incoming" && + call.status === "completed" && + !call.answeredAt + ) { + this.$emit(call, { + id: call.id, + summary: `New Incoming Call Completed: ${call.participants[0]}`, + ts: Date.parse(call.createdAt), + }); + } + }); + + if (calls.length > 0) { + const lastCreatedAt = calls.reduce((max, call) => + Date.parse(call.createdAt) > max + ? Date.parse(call.createdAt) + : max, 0); + this._setLastCreatedAt(lastCreatedAt); + } + }, +}; diff --git a/components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs b/components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs new file mode 100644 index 0000000000000..1fb8da1811374 --- /dev/null +++ b/components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs @@ -0,0 +1,58 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import openphone from "../../openphone.app.mjs"; + +export default { + key: "openphone-new-outgoing-call-completed", + name: "New Outgoing Call Completed", + description: "Emit new event when an outgoing call has ended. [See the documentation](https://www.openphone.com/docs/api-reference/calls/list-calls)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + openphone, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastTimestamp() { + return this.db.get("lastTimestamp") || new Date(0).toISOString(); + }, + _setLastTimestamp(timestamp) { + this.db.set("lastTimestamp", timestamp); + }, + async _getCompletedOutgoingCalls(createdAfter, maxResults) { + const params = { + createdAfter, + maxResults, + }; + const calls = await this.openphone._makeRequest({ + path: "/v1/calls", + params, + }); + return calls.filter((call) => call.status === "completed" && call.direction === "outgoing"); + }, + }, + async run() { + const lastTimestamp = this._getLastTimestamp(); + const calls = await this._getCompletedOutgoingCalls(lastTimestamp, 50); + + calls.forEach((call) => { + this.$emit(call, { + id: call.id, + summary: `Completed call with ${call.participants.join(", ")}`, + ts: Date.parse(call.completedAt), + }); + }); + + if (calls.length > 0) { + this._setLastTimestamp(calls[0].completedAt); + } + }, +}; From 164df465a2f8cde52358bb8b35a533116e3337e7 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 4 Nov 2024 18:22:11 -0300 Subject: [PATCH 2/4] [Components] openphone #14493 Sources - New Call Recording Completed (Instant) - New Outgoing Call Completed (Instant) - New Incoming Call Completed (Instant) Actions - Send Message - Create Contact - Update Contact --- .../actions/create-contact/create-contact.mjs | 78 +++++---- .../actions/send-message/send-message.mjs | 67 ++++---- .../update-contact.mjs} | 36 ++-- components/openphone/common/utils.mjs | 26 +++ components/openphone/openphone.app.mjs | 161 +++++------------- components/openphone/package.json | 5 +- components/openphone/sources/common/base.mjs | 62 +++++++ .../new-call-recording-completed-instant.mjs | 101 ++--------- .../test-event.mjs | 30 ++++ .../new-incoming-call-completed-instant.mjs | 27 +++ .../test-event.mjs | 28 +++ .../new-incoming-call-completed.mjs | 125 -------------- .../new-outgoing-call-completed-instant.mjs | 27 +++ .../test-event.mjs | 28 +++ .../new-outgoing-call-completed.mjs | 58 ------- 15 files changed, 384 insertions(+), 475 deletions(-) rename components/openphone/actions/{create-update-contact/create-update-contact.mjs => update-contact/update-contact.mjs} (67%) create mode 100644 components/openphone/common/utils.mjs create mode 100644 components/openphone/sources/common/base.mjs create mode 100644 components/openphone/sources/new-call-recording-completed-instant/test-event.mjs create mode 100644 components/openphone/sources/new-incoming-call-completed-instant/new-incoming-call-completed-instant.mjs create mode 100644 components/openphone/sources/new-incoming-call-completed-instant/test-event.mjs delete mode 100644 components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs create mode 100644 components/openphone/sources/new-outgoing-call-completed-instant/new-outgoing-call-completed-instant.mjs create mode 100644 components/openphone/sources/new-outgoing-call-completed-instant/test-event.mjs delete mode 100644 components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs diff --git a/components/openphone/actions/create-contact/create-contact.mjs b/components/openphone/actions/create-contact/create-contact.mjs index 77021f0482ff3..be3f6626c2ad4 100644 --- a/components/openphone/actions/create-contact/create-contact.mjs +++ b/components/openphone/actions/create-contact/create-contact.mjs @@ -1,5 +1,5 @@ +import { parseObject } from "../../common/utils.mjs"; import openphone from "../../openphone.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "openphone-create-contact", @@ -10,61 +10,71 @@ export default { props: { openphone, firstName: { - type: "string", - label: "First Name", - description: "The contact's first name.", + propDefinition: [ + openphone, + "firstName", + ], }, lastName: { - type: "string", - label: "Last Name", - description: "The contact's last name.", + propDefinition: [ + openphone, + "lastName", + ], optional: true, }, company: { - type: "string", - label: "Company", - description: "The contact's company name.", + propDefinition: [ + openphone, + "company", + ], optional: true, }, role: { - type: "string", - label: "Role", - description: "The contact's role.", + propDefinition: [ + openphone, + "role", + ], optional: true, }, emails: { - type: "string[]", - label: "Emails", - description: "Array of contact's emails.", + propDefinition: [ + openphone, + "emails", + ], optional: true, }, phoneNumbers: { - type: "string[]", - label: "Phone Numbers", - description: "Array of contact's phone numbers.", + propDefinition: [ + openphone, + "phoneNumbers", + ], optional: true, }, customFields: { - type: "string[]", - label: "Custom Fields", - description: "Array of custom fields for the contact.", + propDefinition: [ + openphone, + "customFields", + ], optional: true, }, }, async run({ $ }) { - const data = { - firstName: this.firstName, - lastName: this.lastName, - company: this.company, - role: this.role, - emails: this.emails, - phoneNumbers: this.phoneNumbers, - customFields: this.customFields, - }; + const response = await this.openphone.createContact({ + $, + data: { + defaultFields: { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: parseObject(this.emails), + phoneNumbers: parseObject(this.phoneNumbers), + }, + customFields: parseObject(this.customFields), + }, + }); - const response = await this.openphone.createContact(data); - - $.export("$summary", `Successfully created contact with ID: ${response.id}`); + $.export("$summary", `Successfully created contact with ID: ${response.data.id}`); return response; }, }; diff --git a/components/openphone/actions/send-message/send-message.mjs b/components/openphone/actions/send-message/send-message.mjs index c1bacee4d6362..c44ccd8ea68ae 100644 --- a/components/openphone/actions/send-message/send-message.mjs +++ b/components/openphone/actions/send-message/send-message.mjs @@ -1,11 +1,11 @@ +import { ConfigurationError } from "@pipedream/platform"; import openphone from "../../openphone.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "openphone-send-message", name: "Send a Text Message via OpenPhone", description: "Send a text message from your OpenPhone number to a recipient. [See the documentation](https://www.openphone.com/docs/api-reference/messages/send-a-text-message)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { openphone, @@ -16,41 +16,42 @@ export default { ], }, to: { - propDefinition: [ - openphone, - "to", - ], + type: "string", + label: "To", + description: "Recipient phone number in E.164 format.", }, content: { - propDefinition: [ - openphone, - "content", - ], - }, - userId: { - propDefinition: [ - openphone, - "userId", - ], - optional: true, - }, - setInboxStatus: { - propDefinition: [ - openphone, - "setInboxStatus", - ], - optional: true, + type: "string", + label: "Content", + description: "The text content of the message to be sent.", }, }, async run({ $ }) { - const response = await this.openphone.sendTextMessage({ - from: this.from, - to: this.to, - content: this.content, - userId: this.userId, - setInboxStatus: this.setInboxStatus, - }); - $.export("$summary", `Successfully sent message to ${this.to}`); - return response; + try { + const response = await this.openphone.sendTextMessage({ + $, + data: { + content: this.content, + from: this.from, + to: [ + this.to, + ], + setInboxStatus: "done", + }, + }); + $.export("$summary", `Successfully sent message to ${this.to}`); + return response; + + } catch ({ response }) { + let errorMessage = ""; + + if (response.data.errors) { + errorMessage = `Prop: ${response.data.errors[0].path} - ${response.data.errors[0].message}`; + } else { + errorMessage = response.data.message; + } + + throw new ConfigurationError(errorMessage); + } }, }; diff --git a/components/openphone/actions/create-update-contact/create-update-contact.mjs b/components/openphone/actions/update-contact/update-contact.mjs similarity index 67% rename from components/openphone/actions/create-update-contact/create-update-contact.mjs rename to components/openphone/actions/update-contact/update-contact.mjs index b824634ddba37..d685eb793b0d9 100644 --- a/components/openphone/actions/create-update-contact/create-update-contact.mjs +++ b/components/openphone/actions/update-contact/update-contact.mjs @@ -1,18 +1,18 @@ +import { parseObject } from "../../common/utils.mjs"; import openphone from "../../openphone.app.mjs"; export default { key: "openphone-update-contact", name: "Update Contact", description: "Update an existing contact on OpenPhone. [See the documentation](https://www.openphone.com/docs/api-reference/contacts/update-a-contact-by-id)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { openphone, contactId: { - propDefinition: [ - openphone, - "contactId", - ], + type: "string", + label: "Contact ID", + description: "The unique identifier of the contact.", }, firstName: { propDefinition: [ @@ -65,17 +65,21 @@ export default { }, }, async run({ $ }) { - const data = { - firstName: this.firstName, - lastName: this.lastName, - company: this.company, - role: this.role, - emails: this.emails, - phoneNumbers: this.phoneNumbers, - customFields: this.customFields, - }; - - const response = await this.openphone.updateContact(this.contactId, data); + const response = await this.openphone.updateContact({ + $, + contactId: this.contactId, + data: { + defaultFields: { + firstName: this.firstName, + lastName: this.lastName, + company: this.company, + role: this.role, + emails: parseObject(this.emails), + phoneNumbers: parseObject(this.phoneNumbers), + }, + customFields: parseObject(this.customFields), + }, + }); $.export("$summary", `Successfully updated contact with ID ${this.contactId}`); return response; diff --git a/components/openphone/common/utils.mjs b/components/openphone/common/utils.mjs new file mode 100644 index 0000000000000..e7c267cec0d5e --- /dev/null +++ b/components/openphone/common/utils.mjs @@ -0,0 +1,26 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + let parsedObj = obj; + if (typeof obj === "string") { + try { + parsedObj = JSON.parse(obj); + } catch (e) { + return obj; + } + } + + if (Array.isArray(parsedObj)) { + return parsedObj.map((item) => parseObject(item)); + } + if (typeof parsedObj === "object") { + for (const [ + key, + value, + ] of Object.entries(parsedObj)) { + parsedObj[key] = parseObject(value); + } + } + + return parsedObj; +}; diff --git a/components/openphone/openphone.app.mjs b/components/openphone/openphone.app.mjs index 4431c38522808..7d126dd084736 100644 --- a/components/openphone/openphone.app.mjs +++ b/components/openphone/openphone.app.mjs @@ -4,65 +4,20 @@ export default { type: "app", app: "openphone", propDefinitions: { - resourceIds: { - type: "string[]", - label: "Resource IDs", - description: "The unique identifiers of phone numbers associated with the webhook.", - optional: true, - async options() { - const phoneNumbers = await this.listPhoneNumbers(); - return phoneNumbers.map((phone) => ({ - label: phone.formattedNumber, - value: phone.id, - })); - }, - }, - label: { - type: "string", - label: "Label", - description: "Webhook's label", - optional: true, - }, from: { type: "string", label: "From", description: "The sender's phone number. Can be either your OpenPhone phone number ID or the full phone number in E.164 format.", async options() { - const phoneNumbers = await this.listPhoneNumbers(); - return phoneNumbers.map((phone) => ({ - label: phone.formattedNumber, - value: phone.id, + const { data } = await this.listPhoneNumbers(); + return data.map(({ + id: value, name, formattedNumber, + }) => ({ + label: `${name} - ${formattedNumber}`, + value, })); }, }, - to: { - type: "string[]", - label: "To", - description: "Array of recipient phone numbers in E.164 format. Currently only supports one recipient.", - }, - content: { - type: "string", - label: "Content", - description: "The text content of the message to be sent.", - }, - userId: { - type: "string", - label: "User ID", - description: "The unique identifier of the OpenPhone user sending the message.", - optional: true, - }, - setInboxStatus: { - type: "string", - label: "Inbox Status", - description: "Set the status of the related OpenPhone inbox conversation.", - optional: true, - options: [ - { - label: "Done", - value: "done", - }, - ], - }, firstName: { type: "string", label: "First Name", @@ -89,109 +44,77 @@ export default { emails: { type: "string[]", label: "Emails", - description: "Array of contact's emails.", - optional: true, + description: "Array of objects of contact's emails. **Example:** `{\"name\": \"Company Email\", \"value\": \"abc@example.com\"}`.", }, phoneNumbers: { type: "string[]", label: "Phone Numbers", - description: "Array of contact's phone numbers.", - optional: true, + description: "Array of objects of contact's phone numbers. **Example:** `{\"name\": \"Company Phone\", \"value\": \"+12345678901\"}`.", }, customFields: { type: "string[]", label: "Custom Fields", - description: "Array of custom fields for the contact.", - optional: true, - }, - contactId: { - type: "string", - label: "Contact ID", - description: "The unique identifier of the contact.", - }, - recording: { - type: "boolean", - label: "Voicemail Recording", - description: "Include voicemail recording in the response.", - }, - transcription: { - type: "boolean", - label: "Voicemail Transcription", - description: "Include voicemail transcription in the response.", + description: "Array of objects of custom fields for the contact. **Example:** `{\"key\": \"inbound-lead\", \"value\": \"[\"option1\", \"option2\"]\"}`.", }, }, methods: { _baseUrl() { - return "https://api.openphone.com"; + return "https://api.openphone.com/v1"; + }, + _headers() { + return { + Authorization: `${this.$auth.api_key}`, + }; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - ...otherOpts, - method, url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.api_key}`, - }, + headers: this._headers(), + ...opts, }); }, - async listPhoneNumbers(opts = {}) { + listPhoneNumbers(opts = {}) { return this._makeRequest({ - path: "/v1/phone-numbers", + path: "/phone-numbers", ...opts, }); }, - async createWebhook(data) { + createWebhook(opts = {}) { return this._makeRequest({ method: "POST", - path: "/v1/webhooks/calls", - data, + path: "/webhooks/calls", + ...opts, + }); + }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, }); }, - async sendTextMessage(data) { + sendTextMessage(opts = {}) { return this._makeRequest({ method: "POST", - path: "/v1/messages", - data: { - from: this.from, - to: this.to, - content: this.content, - userId: this.userId, - setInboxStatus: this.setInboxStatus, - }, + path: "/messages", + ...opts, }); }, - async createContact(data) { + createContact(opts = {}) { return this._makeRequest({ method: "POST", - path: "/v1/contacts", - data: { - firstName: this.firstName, - lastName: this.lastName, - company: this.company, - role: this.role, - emails: this.emails, - phoneNumbers: this.phoneNumbers, - customFields: this.customFields, - }, + path: "/contacts", + ...opts, }); }, - async updateContact(contactId, data) { + updateContact({ + contactId, ...opts + }) { return this._makeRequest({ method: "PATCH", - path: `/v1/contacts/${contactId}`, - data: { - firstName: this.firstName, - lastName: this.lastName, - company: this.company, - role: this.role, - emails: this.emails, - phoneNumbers: this.phoneNumbers, - customFields: this.customFields, - }, + path: `/contacts/${contactId}`, + ...opts, }); }, }, diff --git a/components/openphone/package.json b/components/openphone/package.json index 62226a670a982..e7fae1c54b357 100644 --- a/components/openphone/package.json +++ b/components/openphone/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/openphone", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream OpenPhone Components", "main": "openphone.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/openphone/sources/common/base.mjs b/components/openphone/sources/common/base.mjs new file mode 100644 index 0000000000000..75921c5b6df7d --- /dev/null +++ b/components/openphone/sources/common/base.mjs @@ -0,0 +1,62 @@ +import openphone from "../../openphone.app.mjs"; + +export default { + props: { + openphone, + http: "$.interface.http", + db: "$.service.db", + resourceIds: { + propDefinition: [ + openphone, + "from", + ], + type: "string[]", + label: "Resource IDs", + description: "The unique identifiers of phone numbers associated with the webhook.", + optional: true, + }, + label: { + type: "string", + label: "Label", + description: "Webhook's label", + optional: true, + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getEventFilter() { + return true; + }, + }, + hooks: { + async activate() { + const response = await this.openphone.createWebhook({ + data: { + url: this.http.endpoint, + events: this.getEvent(), + resourceIds: this.resourceIds, + label: this.label, + }, + }); + this._setHookId(response.data.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.openphone.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + if (this.getEventFilter(body)) { + this.$emit(body, { + id: body.id, + summary: this.getSummary(body), + ts: Date.parse(body.data.object.completedAt), + }); + } + }, +}; diff --git a/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs index b2ebf490b561f..ea3404d101b66 100644 --- a/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs +++ b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs @@ -1,100 +1,23 @@ -import openphone from "../../openphone.app.mjs"; -import crypto from "crypto"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "openphone-new-call-recording-completed-instant", name: "New Call Recording Completed", - description: "Emit a new event when a call recording has finished. [See the documentation](https://www.openphone.com/docs/api-reference/webhooks/create-a-new-webhook-for-calls)", - version: "0.0.{{ts}}", + description: "Emit new event when a call recording has finished.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - openphone, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - resourceIds: { - propDefinition: [ - openphone, - "resourceIds", - ], - optional: true, - }, - label: { - propDefinition: [ - openphone, - "label", - ], - optional: true, - }, - }, - hooks: { - async deploy() { - const webhooks = await this.openphone.listWebhooks(); - for (const webhook of webhooks.slice(0, 50)) { - this.$emit(webhook, { - id: webhook.id, - summary: `Webhook ID: ${webhook.id}`, - ts: Date.parse(webhook.createdAt), - }); - } - }, - async activate() { - const response = await this.openphone.createWebhook({ - url: this.http.endpoint, - events: [ - "call.recording.completed", - ], - resourceIds: this.resourceIds, - label: this.label, - }); - this.setWebhookId(response.id); - }, - async deactivate() { - const webhookId = this.getWebhookId(); - if (webhookId) { - await this.openphone.deleteWebhook(webhookId); - this.setWebhookId(null); - } - }, - }, methods: { - getWebhookId() { - return this.db.get("webhookId"); + getEvent() { + return [ + "call.recording.completed", + ]; }, - setWebhookId(id) { - this.db.set("webhookId", id); + getEmit(body) { + return `New call recording completed for call ID: ${body.data.object.id}`; }, }, - async run(event) { - const rawBody = event.bodyRaw; - const secretKey = this.openphone.$auth.api_key; - const webhookSignature = event.headers["x-openphone-signature"]; - - const computedSignature = crypto.createHmac("sha256", secretKey).update(rawBody) - .digest("base64"); - - if (computedSignature !== webhookSignature) { - this.http.respond({ - status: 401, - body: "Unauthorized", - }); - return; - } - - this.http.respond({ - status: 200, - body: "OK", - }); - - const data = event.body; - this.$emit(data, { - id: data.id, - summary: `New call recording completed for call ID: ${data.callId}`, - ts: Date.parse(data.completedAt), - }); - }, + sampleEmit, }; diff --git a/components/openphone/sources/new-call-recording-completed-instant/test-event.mjs b/components/openphone/sources/new-call-recording-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..bd109bda4fb0f --- /dev/null +++ b/components/openphone/sources/new-call-recording-completed-instant/test-event.mjs @@ -0,0 +1,30 @@ +export default { + "apiVersion": "v3", + "createdAt": "2022-01-24T19:30:55.400Z", + "data": { + "object": { + "answeredAt": "2022-01-24T19:30:38.000Z", + "completedAt": "2022-01-24T19:30:48.000Z", + "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79", + "createdAt": "2022-01-24T19:30:34.675Z", + "direction": "incoming", + "from": "+18005550100", + "media": [ + { + "duration":7, + "type": "audio/mpeg", + "url": "https://storage.googleapis.com/opstatics-dev/a5f839bc72a24b33a7fc032f78777146.mp3" + } + ], + "object": "call", + "phoneNumberId": "PNtoDbDhuz", + "status": "completed", + "to": "+18885550101", + "userId": "USu5AsEHuQ", + "voicemail":null + } + }, + "id": "EVda6e196255814311aaac1983005fa2d9", + "object": "event", + "type": "call.recording.completed" +} \ No newline at end of file diff --git a/components/openphone/sources/new-incoming-call-completed-instant/new-incoming-call-completed-instant.mjs b/components/openphone/sources/new-incoming-call-completed-instant/new-incoming-call-completed-instant.mjs new file mode 100644 index 0000000000000..dade14952b2a6 --- /dev/null +++ b/components/openphone/sources/new-incoming-call-completed-instant/new-incoming-call-completed-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "openphone-new-incoming-call-completed-instant", + name: "New Incoming Call Completed (Instant)", + description: "Emit new event when an incoming call is completed, including calls not picked up or voicemails left.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvent() { + return [ + "call.completed", + ]; + }, + getSummary() { + return "New Incoming Call Completed"; + }, + getEventFilter(body) { + return body.data.object.direction === "incoming"; + }, + }, + sampleEmit, +}; diff --git a/components/openphone/sources/new-incoming-call-completed-instant/test-event.mjs b/components/openphone/sources/new-incoming-call-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..350b4db7cad36 --- /dev/null +++ b/components/openphone/sources/new-incoming-call-completed-instant/test-event.mjs @@ -0,0 +1,28 @@ +export default { + "apiVersion": "v3", + "createdAt": "2022-01-24T19:22:25.427Z", + "data": { + "object": { + "answeredAt": null, + "completedAt": "2022-01-24T19:22:19.000Z", + "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79", + "createdAt": "2022-01-24T19:21:59.545Z", + "direction": "incoming", + "from": "+18005550100", + "media": [], + "object": "call", + "phoneNumberId": "PNtoDbDhuz", + "status": "completed", + "to": "+18885550101", + "userId": "USu5AsEHuQ", + "voicemail": { + "duration": 7, + "type": "audio/mpeg", + "url": "https://m.openph.one/static/15ad4740be6048e4a80efb268d347482.mp3" + } + } + }, + "id": "EVd39d3c8d6f244d21a9131de4fc9350d0", + "object": "event", + "type": "call.completed" +} \ No newline at end of file diff --git a/components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs b/components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs deleted file mode 100644 index 23c9f7a3eb710..0000000000000 --- a/components/openphone/sources/new-incoming-call-completed/new-incoming-call-completed.mjs +++ /dev/null @@ -1,125 +0,0 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import openphone from "../../openphone.app.mjs"; - -export default { - key: "openphone-new-incoming-call-completed", - name: "New Incoming Call Completed", - description: "Emit new event when an incoming call is completed, including calls not picked up or voicemails left. [See the documentation](https://www.openphone.com/docs/api-reference/calls/list-calls)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - openphone, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - phoneNumberId: { - type: "string", - label: "Phone Number ID", - description: "The unique identifier of the OpenPhone number associated with the call.", - async options() { - const phoneNumbers = await this.openphone.listPhoneNumbers(); - return phoneNumbers.map((phone) => ({ - label: phone.formattedNumber, - value: phone.id, - })); - }, - }, - recording: { - propDefinition: [ - openphone, - "recording", - ], - }, - transcription: { - propDefinition: [ - openphone, - "transcription", - ], - }, - }, - methods: { - _getLastCreatedAt() { - return this.db.get("lastCreatedAt") || null; - }, - _setLastCreatedAt(createdAt) { - this.db.set("lastCreatedAt", createdAt); - }, - async _fetchCalls(params) { - return this.openphone._makeRequest({ - path: "/v1/calls", - params, - }); - }, - }, - hooks: { - async deploy() { - const params = { - phoneNumberId: this.phoneNumberId, - maxResults: 50, - }; - const calls = await this._fetchCalls(params); - - calls.forEach((call) => { - if ( - call.direction === "incoming" && - call.status === "completed" && - !call.answeredAt - ) { - this.$emit(call, { - id: call.id, - summary: `New Incoming Call Completed: ${call.participants[0]}`, - ts: Date.parse(call.createdAt), - }); - } - }); - - if (calls.length > 0) { - const lastCreatedAt = calls.reduce((max, call) => - Date.parse(call.createdAt) > max - ? Date.parse(call.createdAt) - : max, 0); - this._setLastCreatedAt(lastCreatedAt); - } - }, - }, - async run() { - const lastCreatedAt = this._getLastCreatedAt(); - const params = { - phoneNumberId: this.phoneNumberId, - createdAfter: lastCreatedAt - ? new Date(lastCreatedAt).toISOString() - : undefined, - maxResults: 50, - }; - const calls = await this._fetchCalls(params); - - calls.forEach((call) => { - if ( - call.direction === "incoming" && - call.status === "completed" && - !call.answeredAt - ) { - this.$emit(call, { - id: call.id, - summary: `New Incoming Call Completed: ${call.participants[0]}`, - ts: Date.parse(call.createdAt), - }); - } - }); - - if (calls.length > 0) { - const lastCreatedAt = calls.reduce((max, call) => - Date.parse(call.createdAt) > max - ? Date.parse(call.createdAt) - : max, 0); - this._setLastCreatedAt(lastCreatedAt); - } - }, -}; diff --git a/components/openphone/sources/new-outgoing-call-completed-instant/new-outgoing-call-completed-instant.mjs b/components/openphone/sources/new-outgoing-call-completed-instant/new-outgoing-call-completed-instant.mjs new file mode 100644 index 0000000000000..cdd01cfa2a1cd --- /dev/null +++ b/components/openphone/sources/new-outgoing-call-completed-instant/new-outgoing-call-completed-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "openphone-new-outgoing-call-completed-instant", + name: "New Outgoing Call Completed (Instant)", + description: "Emit new event when an outgoing call has ended.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEvent() { + return [ + "call.completed", + ]; + }, + getSummary() { + return "New Outgoing Call Completed"; + }, + getEventFilter(body) { + return body.data.object.direction === "outgoing"; + }, + }, + sampleEmit, +}; diff --git a/components/openphone/sources/new-outgoing-call-completed-instant/test-event.mjs b/components/openphone/sources/new-outgoing-call-completed-instant/test-event.mjs new file mode 100644 index 0000000000000..3451381272c21 --- /dev/null +++ b/components/openphone/sources/new-outgoing-call-completed-instant/test-event.mjs @@ -0,0 +1,28 @@ +export default { + "apiVersion": "v3", + "createdAt": "2022-01-24T19:22:25.427Z", + "data": { + "object": { + "answeredAt": null, + "completedAt": "2022-01-24T19:22:19.000Z", + "conversationId": "CN78ba0373683c48fd8fd96bc836c51f79", + "createdAt": "2022-01-24T19:21:59.545Z", + "direction": "outgoing", + "from": "+18005550100", + "media": [], + "object": "call", + "phoneNumberId": "PNtoDbDhuz", + "status": "completed", + "to": "+18885550101", + "userId": "USu5AsEHuQ", + "voicemail": { + "duration": 7, + "type": "audio/mpeg", + "url": "https://m.openph.one/static/15ad4740be6048e4a80efb268d347482.mp3" + } + } + }, + "id": "EVd39d3c8d6f244d21a9131de4fc9350d0", + "object": "event", + "type": "call.completed" +} \ No newline at end of file diff --git a/components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs b/components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs deleted file mode 100644 index 1fb8da1811374..0000000000000 --- a/components/openphone/sources/new-outgoing-call-completed/new-outgoing-call-completed.mjs +++ /dev/null @@ -1,58 +0,0 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import openphone from "../../openphone.app.mjs"; - -export default { - key: "openphone-new-outgoing-call-completed", - name: "New Outgoing Call Completed", - description: "Emit new event when an outgoing call has ended. [See the documentation](https://www.openphone.com/docs/api-reference/calls/list-calls)", - version: "0.0.1", - type: "source", - dedupe: "unique", - props: { - openphone, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - }, - methods: { - _getLastTimestamp() { - return this.db.get("lastTimestamp") || new Date(0).toISOString(); - }, - _setLastTimestamp(timestamp) { - this.db.set("lastTimestamp", timestamp); - }, - async _getCompletedOutgoingCalls(createdAfter, maxResults) { - const params = { - createdAfter, - maxResults, - }; - const calls = await this.openphone._makeRequest({ - path: "/v1/calls", - params, - }); - return calls.filter((call) => call.status === "completed" && call.direction === "outgoing"); - }, - }, - async run() { - const lastTimestamp = this._getLastTimestamp(); - const calls = await this._getCompletedOutgoingCalls(lastTimestamp, 50); - - calls.forEach((call) => { - this.$emit(call, { - id: call.id, - summary: `Completed call with ${call.participants.join(", ")}`, - ts: Date.parse(call.completedAt), - }); - }); - - if (calls.length > 0) { - this._setLastTimestamp(calls[0].completedAt); - } - }, -}; From 05fd8034b1e69560645b10f48c1277a47c4482a4 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 4 Nov 2024 18:23:31 -0300 Subject: [PATCH 3/4] pnpm update --- pnpm-lock.yaml | 107 +++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60c2c241b41ba..b966f4f91344f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6997,7 +6997,10 @@ importers: '@pipedream/platform': 3.0.1 components/openphone: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/opensea: specifiers: @@ -13263,55 +13266,6 @@ packages: - aws-crt dev: false - /@aws-sdk/client-sso-oidc/3.600.0_tdq3komn4zwyd65w7klbptsu34: - resolution: {integrity: sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw==} - engines: {node: '>=16.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.600.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.3 - '@smithy/core': 2.2.3 - '@smithy/fetch-http-handler': 3.2.1 - '@smithy/hash-node': 3.0.2 - '@smithy/invalid-dependency': 3.0.2 - '@smithy/middleware-content-length': 3.0.2 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.6 - '@smithy/middleware-serde': 3.0.3 - '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.2 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.6 - '@smithy/types': 3.3.0 - '@smithy/url-parser': 3.0.3 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.6 - '@smithy/util-defaults-mode-node': 3.0.6 - '@smithy/util-endpoints': 2.0.3 - '@smithy/util-middleware': 3.0.3 - '@smithy/util-retry': 3.0.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - transitivePeerDependencies: - - '@aws-sdk/client-sts' - - aws-crt - dev: false - /@aws-sdk/client-sso/3.423.0: resolution: {integrity: sha512-znIufHkwhCIePgaYciIs3x/+BpzR57CZzbCKHR9+oOvGyufEPPpUT5bFLvbwTgfiVkTjuk6sG/ES3U5Bc+xtrA==} engines: {node: '>=14.0.0'} @@ -13547,7 +13501,7 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0_tdq3komn4zwyd65w7klbptsu34 + '@aws-sdk/client-sso-oidc': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 '@aws-sdk/middleware-host-header': 3.598.0 @@ -13589,6 +13543,55 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sts/3.600.0_dseaa2p5u2yk67qiepewcq3hkq: + resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.600.0 + '@aws-sdk/core': 3.598.0 + '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 + '@aws-sdk/middleware-host-header': 3.598.0 + '@aws-sdk/middleware-logger': 3.598.0 + '@aws-sdk/middleware-recursion-detection': 3.598.0 + '@aws-sdk/middleware-user-agent': 3.598.0 + '@aws-sdk/region-config-resolver': 3.598.0 + '@aws-sdk/types': 3.598.0 + '@aws-sdk/util-endpoints': 3.598.0 + '@aws-sdk/util-user-agent-browser': 3.598.0 + '@aws-sdk/util-user-agent-node': 3.598.0 + '@smithy/config-resolver': 3.0.3 + '@smithy/core': 2.2.3 + '@smithy/fetch-http-handler': 3.2.1 + '@smithy/hash-node': 3.0.2 + '@smithy/invalid-dependency': 3.0.2 + '@smithy/middleware-content-length': 3.0.2 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.6 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.3 + '@smithy/node-http-handler': 3.1.2 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.6 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.6 + '@smithy/util-defaults-mode-node': 3.0.6 + '@smithy/util-endpoints': 2.0.3 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.2 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + dev: false + /@aws-sdk/core/3.556.0: resolution: {integrity: sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA==} engines: {node: '>=14.0.0'} @@ -17886,7 +17889,7 @@ packages: '@aws-sdk/client-sns': 3.423.0 '@aws-sdk/client-sqs': 3.423.0 '@aws-sdk/client-ssm': 3.423.0 - '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/client-sts': 3.600.0_dseaa2p5u2yk67qiepewcq3hkq '@aws-sdk/s3-request-presigner': 3.609.0 '@pipedream/helper_functions': 0.3.12 '@pipedream/platform': 1.6.6 From 83516d75ae356e7eb2af748904781daf9475a942 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Wed, 6 Nov 2024 11:38:29 -0300 Subject: [PATCH 4/4] fix source --- .../new-call-recording-completed-instant.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs index ea3404d101b66..9a0098f0fbe39 100644 --- a/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs +++ b/components/openphone/sources/new-call-recording-completed-instant/new-call-recording-completed-instant.mjs @@ -10,6 +10,7 @@ export default { type: "source", dedupe: "unique", methods: { + ...common.methods, getEvent() { return [ "call.recording.completed",