@pipedream/salesforce_rest_api 0.3.3

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.
Files changed (37) hide show
  1. package/LICENSE +7 -0
  2. package/actions/salesforce-create-account/salesforce-create-account.mjs +374 -0
  3. package/actions/salesforce-create-attachment/salesforce-create-attachment.mjs +73 -0
  4. package/actions/salesforce-create-campaign/salesforce-create-campaign.mjs +148 -0
  5. package/actions/salesforce-create-case/salesforce-create-case.mjs +179 -0
  6. package/actions/salesforce-create-casecomment/salesforce-create-casecomment.mjs +58 -0
  7. package/actions/salesforce-create-contact/salesforce-create-contact.mjs +340 -0
  8. package/actions/salesforce-create-event/salesforce-create-event.mjs +232 -0
  9. package/actions/salesforce-create-lead/salesforce-create-lead.mjs +267 -0
  10. package/actions/salesforce-create-note/salesforce-create-note.mjs +63 -0
  11. package/actions/salesforce-create-opportunity/salesforce-create-opportunity.mjs +170 -0
  12. package/actions/salesforce-create-record/salesforce-create-record.mjs +36 -0
  13. package/actions/salesforce-create-task/salesforce-create-task.mjs +219 -0
  14. package/actions/salesforce-delete-opportunity/salesforce-delete-opportunity.mjs +37 -0
  15. package/actions/salesforce-get-sobject-fields-values/salesforce-get-sobject-fields-values.mjs +37 -0
  16. package/actions/salesforce-insert-blob-data/salesforce-insert-blob-data.mjs +70 -0
  17. package/actions/salesforce-make-api-call/salesforce-make-api-call.mjs +56 -0
  18. package/actions/salesforce-soql-search/salesforce-soql-search.mjs +29 -0
  19. package/actions/salesforce-sosl-search/salesforce-sosl-search.mjs +30 -0
  20. package/actions/salesforce-update-account/salesforce-update-account.mjs +357 -0
  21. package/actions/salesforce-update-contact/salesforce-update-contact.mjs +345 -0
  22. package/actions/salesforce-update-opportunity/salesforce-update-opportunity.mjs +171 -0
  23. package/actions/salesforce-update-record/salesforce-update-record.mjs +40 -0
  24. package/package.json +21 -0
  25. package/salesforce_rest_api.app.mjs +247 -0
  26. package/sources/common-instant.mjs +134 -0
  27. package/sources/common.mjs +96 -0
  28. package/sources/new-object/new-object.mjs +69 -0
  29. package/sources/new-object-instant/new-object-instant.mjs +35 -0
  30. package/sources/new-outbound-message/new-outbound-message.mjs +105 -0
  31. package/sources/object-deleted/object-deleted.mjs +52 -0
  32. package/sources/object-deleted-instant/object-deleted-instant.mjs +36 -0
  33. package/sources/object-updated/object-updated.mjs +63 -0
  34. package/sources/object-updated-instant/object-updated-instant.mjs +36 -0
  35. package/sources/updated-field-on-record/updated-field-on-record.mjs +179 -0
  36. package/sources/updated-field-on-record-instant/updated-field-on-record-instant.mjs +76 -0
  37. package/utils.mjs +27 -0
@@ -0,0 +1,247 @@
1
+ import { axios } from "@pipedream/platform";
2
+ import salesforceWebhooks from "salesforce-webhooks";
3
+ const { SalesforceClient } = salesforceWebhooks;
4
+
5
+ export default {
6
+ type: "app",
7
+ app: "salesforce_rest_api",
8
+ propDefinitions: {
9
+ objectType: {
10
+ type: "string",
11
+ label: "Object Type",
12
+ description: "The type of object for which to monitor events",
13
+ async options(context) {
14
+ const {
15
+ page,
16
+ eventType,
17
+ } = context;
18
+ if (page !== 0) {
19
+ // The list of allowed SObject types is static and exhaustively
20
+ // provided through a single method call
21
+ return [];
22
+ }
23
+
24
+ const supportedObjectTypes = this.listAllowedSObjectTypes(eventType);
25
+ const options = supportedObjectTypes.map((i) => ({
26
+ label: i.label,
27
+ value: i.name,
28
+ }));
29
+ return {
30
+ options,
31
+ };
32
+ },
33
+ },
34
+ field: {
35
+ type: "string",
36
+ label: "Field",
37
+ description: "The object field to watch for changes",
38
+ async options(context) {
39
+ const {
40
+ page,
41
+ objectType,
42
+ } = context;
43
+ if (page !== 0) {
44
+ return {
45
+ options: [],
46
+ };
47
+ }
48
+
49
+ const fields = await this.getFieldsForObjectType(objectType);
50
+ return fields.map((field) => field.name);
51
+ },
52
+ },
53
+ fieldUpdatedTo: {
54
+ type: "string",
55
+ label: "Field Updated to",
56
+ description: "If provided, the trigger will only fire when the updated field is an EXACT MATCH (including spacing and casing) to the value you provide in this field",
57
+ optional: true,
58
+ },
59
+ },
60
+ methods: {
61
+ _authToken() {
62
+ return this.$auth.oauth_access_token;
63
+ },
64
+ _instance() {
65
+ return this.$auth.yourinstance;
66
+ },
67
+ _instanceUrl() {
68
+ return this.$auth.instance_url;
69
+ },
70
+ _subdomain() {
71
+ return (
72
+ this._instance() ||
73
+ this._instanceUrl()
74
+ .replace("https://", "")
75
+ .replace(".salesforce.com", "")
76
+ );
77
+ },
78
+ _apiVersion() {
79
+ return "50.0";
80
+ },
81
+ _baseApiUrl() {
82
+ return (
83
+ this._instanceUrl() ||
84
+ `https://${this._subdomain()}.salesforce.com`
85
+ );
86
+ },
87
+ _userApiUrl() {
88
+ const baseUrl = this._baseApiUrl();
89
+ return `${baseUrl}/services/oauth2/userinfo`;
90
+ },
91
+ _sObjectsApiUrl() {
92
+ const baseUrl = this._baseApiUrl();
93
+ const apiVersion = this._apiVersion();
94
+ return `${baseUrl}/services/data/v${apiVersion}/sobjects`;
95
+ },
96
+ _sObjectTypeDescriptionApiUrl(sObjectType) {
97
+ const baseUrl = this._sObjectsApiUrl();
98
+ return `${baseUrl}/${sObjectType}/describe`;
99
+ },
100
+ _sObjectTypeUpdatedApiUrl(sObjectType) {
101
+ const baseUrl = this._sObjectsApiUrl();
102
+ return `${baseUrl}/${sObjectType}/updated`;
103
+ },
104
+ _sObjectTypeDeletedApiUrl(sObjectType) {
105
+ const baseUrl = this._sObjectsApiUrl();
106
+ return `${baseUrl}/${sObjectType}/deleted`;
107
+ },
108
+ _sObjectDetailsApiUrl(sObjectType, id) {
109
+ const baseUrl = this._sObjectsApiUrl();
110
+ return `${baseUrl}/${sObjectType}/${id}`;
111
+ },
112
+ _makeRequestHeaders() {
113
+ const authToken = this._authToken();
114
+ return {
115
+ "Authorization": `Bearer ${authToken}`,
116
+ "User-Agent": "@PipedreamHQ/pipedream v0.1",
117
+ };
118
+ },
119
+ _makeRequestConfig() {
120
+ const headers = this._makeRequestHeaders();
121
+ return {
122
+ method: "GET",
123
+ headers,
124
+ };
125
+ },
126
+ async _makeRequest(opts) {
127
+ const {
128
+ $,
129
+ ...requestOpts
130
+ } = opts;
131
+ const baseRequestConfig = this._makeRequestConfig();
132
+ const requestConfig = {
133
+ ...baseRequestConfig,
134
+ ...requestOpts,
135
+ };
136
+ return axios($ ?? this, requestConfig);
137
+ },
138
+ _formatDateString(dateString) {
139
+ // Remove milliseconds from date ISO string
140
+ return dateString.replace(/\.[0-9]{3}/, "");
141
+ },
142
+ _getSalesforceClient() {
143
+ const clientOpts = {
144
+ apiVersion: this._apiVersion(),
145
+ authToken: this._authToken(),
146
+ instance: this._subdomain(),
147
+ };
148
+ return new SalesforceClient(clientOpts);
149
+ },
150
+ isHistorySObject(sobject) {
151
+ return sobject.associateEntityType === "History" && sobject.name.includes("History");
152
+ },
153
+ listAllowedSObjectTypes(eventType) {
154
+ const verbose = true;
155
+ return SalesforceClient.getAllowedSObjects(eventType, verbose);
156
+ },
157
+ async createWebhook(endpointUrl, sObjectType, event, secretToken, opts) {
158
+ const {
159
+ fieldsToCheck,
160
+ fieldsToCheckMode,
161
+ } = opts;
162
+ const client = this._getSalesforceClient();
163
+ const webhookOpts = {
164
+ endpointUrl,
165
+ sObjectType,
166
+ event,
167
+ secretToken,
168
+ fieldsToCheck,
169
+ fieldsToCheckMode,
170
+ };
171
+ return client.createWebhook(webhookOpts);
172
+ },
173
+ async deleteWebhook(webhookData) {
174
+ const client = this._getSalesforceClient();
175
+ return client.deleteWebhook(webhookData);
176
+ },
177
+ async listSObjectTypes() {
178
+ const url = this._sObjectsApiUrl();
179
+ return this._makeRequest({
180
+ url,
181
+ });
182
+ },
183
+ async getNameFieldForObjectType(objectType) {
184
+ const url = this._sObjectTypeDescriptionApiUrl(objectType);
185
+ const data = await this._makeRequest({
186
+ url,
187
+ });
188
+ const nameField = data.fields.find((f) => f.nameField);
189
+ return nameField !== undefined
190
+ ? nameField.name
191
+ : "Id";
192
+ },
193
+ async getFieldsForObjectType(objectType) {
194
+ const url = this._sObjectTypeDescriptionApiUrl(objectType);
195
+ const data = await this._makeRequest({
196
+ url,
197
+ });
198
+ return data.fields;
199
+ },
200
+ async getHistorySObjectForObjectType(objectType) {
201
+ const { sobjects } = await this.listSObjectTypes();
202
+ const historyObject = sobjects.find(
203
+ (sobject) => sobject.associateParentEntity === objectType
204
+ && this.isHistorySObject(sobject),
205
+ );
206
+ return historyObject;
207
+ },
208
+ async getSObject(objectType, id) {
209
+ const url = this._sObjectDetailsApiUrl(objectType, id);
210
+ return this._makeRequest({
211
+ url,
212
+ });
213
+ },
214
+ async getUpdatedForObjectType(objectType, start, end) {
215
+ const url = this._sObjectTypeUpdatedApiUrl(objectType);
216
+ const params = {
217
+ start: this._formatDateString(start),
218
+ end: this._formatDateString(end),
219
+ };
220
+ return this._makeRequest({
221
+ url,
222
+ params,
223
+ });
224
+ },
225
+ async getDeletedForObjectType(objectType, start, end) {
226
+ const url = this._sObjectTypeDeletedApiUrl(objectType);
227
+ const params = {
228
+ start: this._formatDateString(start),
229
+ end: this._formatDateString(end),
230
+ };
231
+ return this._makeRequest({
232
+ url,
233
+ params,
234
+ });
235
+ },
236
+ async getUserInfo(authToken) {
237
+ const url = this._userApiUrl();
238
+ return this._makeRequest({
239
+ url,
240
+ headers: {
241
+ ...this._makeRequestHeaders(),
242
+ "Authorization": `Bearer ${authToken}`,
243
+ },
244
+ });
245
+ },
246
+ },
247
+ };
@@ -0,0 +1,134 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+
3
+ import salesforce from "../salesforce_rest_api.app.mjs";
4
+
5
+ export default {
6
+ dedupe: "unique",
7
+ props: {
8
+ db: "$.service.db",
9
+ // eslint-disable-next-line pipedream/props-label,pipedream/props-description
10
+ http: {
11
+ type: "$.interface.http",
12
+ customResponse: true,
13
+ },
14
+ salesforce,
15
+ // Not inheriting `objectType` propDefinition from salesforce so `this` in async options has
16
+ // component instance's methods
17
+ objectType: {
18
+ ...salesforce.propDefinitions.objectType,
19
+ label: salesforce.propDefinitions.objectType.label,
20
+ description: salesforce.propDefinitions.objectType.description,
21
+ async options(context) {
22
+ return salesforce.propDefinitions.objectType.options.call(this.salesforce, {
23
+ ...context,
24
+ eventType: this.getEventType(),
25
+ });
26
+ },
27
+ },
28
+ },
29
+ hooks: {
30
+ async activate() {
31
+ // Retrieve metadata about the SObject specified by the user
32
+ const nameField = await this.salesforce.getNameFieldForObjectType(this.objectType);
33
+ this.setNameField(nameField);
34
+
35
+ // Create the webhook in the Salesforce platform
36
+ const secretToken = uuidv4();
37
+ let webhookData;
38
+ try {
39
+ webhookData = await this.salesforce.createWebhook(
40
+ this.http.endpoint,
41
+ this.objectType,
42
+ this.getEventType(),
43
+ secretToken,
44
+ {
45
+ fieldsToCheck: this.getFieldsToCheck(),
46
+ fieldsToCheckMode: this.getFieldsToCheckMode(),
47
+ },
48
+ );
49
+ } catch (err) {
50
+ console.log("Create webhook error:", err.response?.data ?? err);
51
+ throw err;
52
+ }
53
+ this._setSecretToken(secretToken);
54
+ this._setWebhookData(webhookData);
55
+ },
56
+ async deactivate() {
57
+ // Create the webhook from the Salesforce platform
58
+ const webhookData = this._getWebhookData();
59
+ await this.salesforce.deleteWebhook(webhookData);
60
+ },
61
+ },
62
+ methods: {
63
+ _getSecretToken() {
64
+ return this.db.get("secretToken");
65
+ },
66
+ _setSecretToken(secretToken) {
67
+ this.db.set("secretToken", secretToken);
68
+ },
69
+ _getWebhookData() {
70
+ return this.db.get("webhookData");
71
+ },
72
+ _setWebhookData(webhookData) {
73
+ this.db.set("webhookData", webhookData);
74
+ },
75
+ getNameField() {
76
+ return this.db.get("nameField");
77
+ },
78
+ setNameField(nameField) {
79
+ this.db.set("nameField", nameField);
80
+ },
81
+ _isValidSource(event) {
82
+ const webhookToken = event.headers["x-webhook-token"];
83
+ const secretToken = this._getSecretToken();
84
+ return webhookToken === secretToken;
85
+ },
86
+ processEvent(event) {
87
+ const { body } = event;
88
+ const meta = this.generateMeta(event);
89
+ this.$emit(body, meta);
90
+ },
91
+ generateMeta() {
92
+ throw new Error("generateMeta is not implemented");
93
+ },
94
+ getEventType() {
95
+ throw new Error("getEventType is not implemented");
96
+ },
97
+ /**
98
+ * This method returns the fields in the SObject type (e.g. Account, Lead, etc.) that the event
99
+ * source should listen for updates to. This base implementation returns `undefined`, to not
100
+ * necessitate any specific fields to be updated.
101
+ *
102
+ * @returns the fields in the SObject type for which to receive updates
103
+ */
104
+ getFieldsToCheck() {
105
+ return undefined;
106
+ },
107
+ /**
108
+ * This method returns whether the event source should listen for updates where `all` the fields
109
+ * in the SObject are updated, or when `any` of them are. This base implementation returns
110
+ * `undefined` to use to client's default `fieldToCheckMode` (`any`).
111
+ *
112
+ * @returns whether the webhook should receive events when `all` the fields to check are
113
+ * updated, or when `any` of them are
114
+ */
115
+ getFieldsToCheckMode() {
116
+ return undefined;
117
+ },
118
+ },
119
+ async run(event) {
120
+ if (!this._isValidSource(event)) {
121
+ this.http.respond({
122
+ statusCode: 404,
123
+ });
124
+ console.log("Skipping event from unrecognized source");
125
+ return;
126
+ }
127
+
128
+ this.http.respond({
129
+ statusCode: 200,
130
+ });
131
+
132
+ await this.processEvent(event);
133
+ },
134
+ };
@@ -0,0 +1,96 @@
1
+ import salesforce from "../salesforce_rest_api.app.mjs";
2
+
3
+ export default {
4
+ dedupe: "unique",
5
+ props: {
6
+ db: "$.service.db",
7
+ salesforce,
8
+ // eslint-disable-next-line pipedream/props-label,pipedream/props-description
9
+ timer: {
10
+ type: "$.interface.timer",
11
+ default: {
12
+ intervalSeconds: 60 * 15, // 15 minutes
13
+ },
14
+ },
15
+ // Not inheriting `objectType` propDefinition from salesforce so `this` in async options has
16
+ // component instance's methods
17
+ objectType: {
18
+ ...salesforce.propDefinitions.objectType,
19
+ label: salesforce.propDefinitions.objectType.label,
20
+ description: salesforce.propDefinitions.objectType.description,
21
+ async options(context) {
22
+ const { page } = context;
23
+ if (page !== 0) {
24
+ return {
25
+ options: [],
26
+ };
27
+ }
28
+
29
+ const { sobjects } = await this.salesforce.listSObjectTypes();
30
+ const options = sobjects
31
+ .filter(this.isValidSObject)
32
+ .map((sobject) => ({
33
+ label: sobject.label,
34
+ value: sobject.name,
35
+ }));
36
+ return {
37
+ options,
38
+ };
39
+ },
40
+ },
41
+ },
42
+ hooks: {
43
+ async activate() {
44
+ const latestDateCovered = this.getLatestDateCovered();
45
+ if (!latestDateCovered) {
46
+ const now = new Date().toISOString();
47
+ this.setLatestDateCovered(now);
48
+ }
49
+
50
+ const nameField = await this.salesforce.getNameFieldForObjectType(this.objectType);
51
+ this.setNameField(nameField);
52
+ },
53
+ },
54
+ methods: {
55
+ getLatestDateCovered() {
56
+ return this.db.get("latestDateCovered");
57
+ },
58
+ setLatestDateCovered(latestDateCovered) {
59
+ this.db.set("latestDateCovered", latestDateCovered);
60
+ },
61
+ getNameField() {
62
+ return this.db.get("nameField");
63
+ },
64
+ setNameField(nameField) {
65
+ this.db.set("nameField", nameField);
66
+ },
67
+ isValidSObject(sobject) {
68
+ // Only the activity of those SObject types that have the `replicateable`
69
+ // flag set is published via the `getUpdated` API.
70
+ //
71
+ // See the API docs here: https://sforce.co/3gDy3uP
72
+ return sobject.replicateable;
73
+ },
74
+ processEvent() {
75
+ throw new Error("processEvent is not implemented");
76
+ },
77
+ },
78
+ async run(event) {
79
+ const startTimestamp = this.getLatestDateCovered();
80
+ const endTimestamp = new Date(event.timestamp * 1000).toISOString();
81
+ const timeDiffSec = Math.floor(
82
+ (Date.parse(endTimestamp) - Date.parse(startTimestamp)) / 1000,
83
+ );
84
+ if (timeDiffSec < 60) {
85
+ console.log(`
86
+ Skipping execution since the last one happened approximately ${timeDiffSec} seconds ago
87
+ `);
88
+ return;
89
+ }
90
+
91
+ await this.processEvent({
92
+ startTimestamp,
93
+ endTimestamp,
94
+ });
95
+ },
96
+ };
@@ -0,0 +1,69 @@
1
+ import startCase from "lodash/startCase.js";
2
+
3
+ import common from "../common.mjs";
4
+
5
+ export default {
6
+ ...common,
7
+ type: "source",
8
+ name: "New Object (of Selectable Type)",
9
+ key: "salesforce_rest_api-new-object",
10
+ description: "Emit new event (at regular intervals) when an object of arbitrary type (selected as an input parameter by the user) is created. See [the docs](https://sforce.co/3yPSJZy) for more information.",
11
+ version: "0.0.5",
12
+ methods: {
13
+ ...common.methods,
14
+ isItemRelevant(item, startTimestamp, endTimestamp) {
15
+ const startDate = Date.parse(startTimestamp);
16
+ const endDate = Date.parse(endTimestamp);
17
+ const createdDate = Date.parse(item.CreatedDate);
18
+ return startDate <= createdDate && endDate >= createdDate;
19
+ },
20
+ generateMeta(item) {
21
+ const nameField = this.getNameField();
22
+ const {
23
+ CreatedDate: createdDate,
24
+ Id: id,
25
+ [nameField]: name,
26
+ } = item;
27
+ const entityType = startCase(this.objectType);
28
+ const summary = `New ${entityType} created: ${name}`;
29
+ const ts = Date.parse(createdDate);
30
+ return {
31
+ id,
32
+ summary,
33
+ ts,
34
+ };
35
+ },
36
+ async processEvent(eventData) {
37
+ const {
38
+ startTimestamp,
39
+ endTimestamp,
40
+ } = eventData;
41
+ const {
42
+ ids,
43
+ latestDateCovered,
44
+ } = await this.salesforce.getUpdatedForObjectType(
45
+ this.objectType,
46
+ startTimestamp,
47
+ endTimestamp,
48
+ );
49
+
50
+ // By the time we try to retrieve an item, it might've been deleted. This
51
+ // will cause `getSObject` to throw a 404 exception, which will reject its
52
+ // promise. Hence, we need to filter those items that we still in Salesforce
53
+ // and exclude those that are not.
54
+ const itemRetrievals = await Promise.allSettled(
55
+ ids.map((id) => this.salesforce.getSObject(this.objectType, id)),
56
+ );
57
+ itemRetrievals
58
+ .filter((result) => result.status === "fulfilled")
59
+ .map((result) => result.value)
60
+ .filter((item) => this.isItemRelevant(item, startTimestamp, endTimestamp))
61
+ .forEach((item) => {
62
+ const meta = this.generateMeta(item);
63
+ this.$emit(item, meta);
64
+ });
65
+
66
+ this.setLatestDateCovered(latestDateCovered);
67
+ },
68
+ },
69
+ };
@@ -0,0 +1,35 @@
1
+ import startCase from "lodash/startCase.js";
2
+
3
+ import common from "../common-instant.mjs";
4
+
5
+ export default {
6
+ ...common,
7
+ type: "source",
8
+ name: "New Object (Instant, of Selectable Type)",
9
+ key: "salesforce_rest_api-new-object-instant",
10
+ description: "Emit new event immediately after an object of arbitrary type (selected as an input parameter by the user) is created",
11
+ version: "0.0.5",
12
+ methods: {
13
+ ...common.methods,
14
+ generateMeta(data) {
15
+ const nameField = this.getNameField();
16
+ const { New: newObject } = data.body;
17
+ const {
18
+ CreatedDate: createdDate,
19
+ Id: id,
20
+ [nameField]: name,
21
+ } = newObject;
22
+ const entityType = startCase(this.objectType).toLowerCase();
23
+ const summary = `New ${entityType} created: ${name}`;
24
+ const ts = Date.parse(createdDate);
25
+ return {
26
+ id,
27
+ summary,
28
+ ts,
29
+ };
30
+ },
31
+ getEventType() {
32
+ return "new";
33
+ },
34
+ },
35
+ };
@@ -0,0 +1,105 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ import salesforce from "../../salesforce_rest_api.app.mjs";
3
+
4
+ export default {
5
+ type: "source",
6
+ name: "New Outbound Message (Instant)",
7
+ key: "salesforce_rest_api-new-outbound-message",
8
+ description: "Emit new event when a new outbound message is received in Salesforce. See Salesforce's guide on setting up [Outbound Messaging](https://sforce.co/3JbZJom). Set the Outbound Message's Endpoint URL to the endpoint of the created source. The \"Send Session ID\" option must be enabled for validating outbound messages from Salesforce.",
9
+ version: "0.0.2",
10
+ dedupe: "unique",
11
+ props: {
12
+ db: "$.service.db",
13
+ // eslint-disable-next-line pipedream/props-label,pipedream/props-description
14
+ http: {
15
+ type: "$.interface.http",
16
+ customResponse: true,
17
+ },
18
+ salesforce,
19
+ },
20
+ methods: {
21
+ _unwrapMessage(message) {
22
+ const parser = new XMLParser({
23
+ removeNSPrefix: true,
24
+ });
25
+ const obj = parser.parse(message);
26
+ const notifications = obj["Envelope"]["Body"].notifications;
27
+ return notifications;
28
+ },
29
+ _sendHttpResponse(successValue = true, status) {
30
+ // eslint-disable-next-line multiline-ternary
31
+ status = status ?? (successValue ? 200 : 400);
32
+ this.http.respond({
33
+ status,
34
+ body: `
35
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
36
+ xmlns:out="http://soap.sforce.com/2005/09/outbound">
37
+ <soapenv:Header/>
38
+ <soapenv:Body>
39
+ <out:notificationsResponse>
40
+ <out:Ack>${successValue}</out:Ack>
41
+ </out:notificationsResponse>
42
+ </soapenv:Body>
43
+ </soapenv:Envelope>
44
+ `,
45
+ headers: {
46
+ "content-type": "text/xml",
47
+ },
48
+ });
49
+ },
50
+ async _isValidSessionId(sessionId) {
51
+ try {
52
+ const data = await this.salesforce.getUserInfo(sessionId);
53
+ return Boolean(data);
54
+ } catch (err) {
55
+ console.log("Error validating SessionId:", err);
56
+ return false;
57
+ }
58
+ },
59
+ async _isValidSource(data) {
60
+ const { SessionId: sessionId } = data;
61
+ if (!sessionId) {
62
+ throw new Error("The outbound message is missing a Session ID. Please configure the outbound message to send Session ID to validate the webhook source.");
63
+ }
64
+
65
+ return this._isValidSessionId(sessionId);
66
+ },
67
+ generateMeta(data) {
68
+ const {
69
+ ActionId: actionId,
70
+ Notification: { Id: eventId },
71
+ } = data;
72
+ const id = `${eventId}-${actionId}`;
73
+ const summary = JSON.stringify(data);
74
+ const ts = Date.now();
75
+ return {
76
+ id,
77
+ summary,
78
+ ts,
79
+ };
80
+ },
81
+ },
82
+ async run(event) {
83
+ const { bodyRaw } = event;
84
+ this._sendHttpResponse(true);
85
+ const data = this._unwrapMessage(bodyRaw);
86
+ if (!(await this._isValidSource(data))) {
87
+ console.log("Skipping event from unrecognized source");
88
+ return;
89
+ }
90
+
91
+ let notifications = data.Notification;
92
+ if (!Array.isArray(notifications)) {
93
+ notifications = [
94
+ data.Notification,
95
+ ];
96
+ }
97
+
98
+ notifications.forEach((n) => {
99
+ const notification = Object.assign(data, {
100
+ Notification: n,
101
+ });
102
+ this.$emit(notification, this.generateMeta(notification));
103
+ });
104
+ },
105
+ };