@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.
- package/LICENSE +7 -0
- package/actions/salesforce-create-account/salesforce-create-account.mjs +374 -0
- package/actions/salesforce-create-attachment/salesforce-create-attachment.mjs +73 -0
- package/actions/salesforce-create-campaign/salesforce-create-campaign.mjs +148 -0
- package/actions/salesforce-create-case/salesforce-create-case.mjs +179 -0
- package/actions/salesforce-create-casecomment/salesforce-create-casecomment.mjs +58 -0
- package/actions/salesforce-create-contact/salesforce-create-contact.mjs +340 -0
- package/actions/salesforce-create-event/salesforce-create-event.mjs +232 -0
- package/actions/salesforce-create-lead/salesforce-create-lead.mjs +267 -0
- package/actions/salesforce-create-note/salesforce-create-note.mjs +63 -0
- package/actions/salesforce-create-opportunity/salesforce-create-opportunity.mjs +170 -0
- package/actions/salesforce-create-record/salesforce-create-record.mjs +36 -0
- package/actions/salesforce-create-task/salesforce-create-task.mjs +219 -0
- package/actions/salesforce-delete-opportunity/salesforce-delete-opportunity.mjs +37 -0
- package/actions/salesforce-get-sobject-fields-values/salesforce-get-sobject-fields-values.mjs +37 -0
- package/actions/salesforce-insert-blob-data/salesforce-insert-blob-data.mjs +70 -0
- package/actions/salesforce-make-api-call/salesforce-make-api-call.mjs +56 -0
- package/actions/salesforce-soql-search/salesforce-soql-search.mjs +29 -0
- package/actions/salesforce-sosl-search/salesforce-sosl-search.mjs +30 -0
- package/actions/salesforce-update-account/salesforce-update-account.mjs +357 -0
- package/actions/salesforce-update-contact/salesforce-update-contact.mjs +345 -0
- package/actions/salesforce-update-opportunity/salesforce-update-opportunity.mjs +171 -0
- package/actions/salesforce-update-record/salesforce-update-record.mjs +40 -0
- package/package.json +21 -0
- package/salesforce_rest_api.app.mjs +247 -0
- package/sources/common-instant.mjs +134 -0
- package/sources/common.mjs +96 -0
- package/sources/new-object/new-object.mjs +69 -0
- package/sources/new-object-instant/new-object-instant.mjs +35 -0
- package/sources/new-outbound-message/new-outbound-message.mjs +105 -0
- package/sources/object-deleted/object-deleted.mjs +52 -0
- package/sources/object-deleted-instant/object-deleted-instant.mjs +36 -0
- package/sources/object-updated/object-updated.mjs +63 -0
- package/sources/object-updated-instant/object-updated-instant.mjs +36 -0
- package/sources/updated-field-on-record/updated-field-on-record.mjs +179 -0
- package/sources/updated-field-on-record-instant/updated-field-on-record-instant.mjs +76 -0
- 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
|
+
};
|