n8n-nodes-idb2b 3.2.1 → 3.2.4
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/README.md +7 -2
- package/dist/nodes/IDB2B/IDB2B.node.js +8 -18
- package/dist/nodes/IDB2B/descriptions/contactProperties.js +57 -0
- package/dist/nodes/IDB2B/handlers/ContactHandler.js +1 -1
- package/dist/nodes/IDB2B/utils/common.js +72 -0
- package/dist/nodes/IDB2B/utils/validators.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,10 +75,14 @@ npm install n8n-nodes-idb2b
|
|
|
75
75
|
|
|
76
76
|
Required:
|
|
77
77
|
- **Name**: Contact full name
|
|
78
|
-
- **Email**: Valid email address
|
|
79
78
|
|
|
80
79
|
Optional (under Additional Fields):
|
|
81
|
-
- Phone Number
|
|
80
|
+
- **Phone Number**: Can be blank when you only have profile data
|
|
81
|
+
- **Email**: Can be blank
|
|
82
|
+
- **Job Title**, **Owner ID**, **Company ID**, **Status ID**, **Source ID**
|
|
83
|
+
- **LinkedIn URL**: Added as a social link payload
|
|
84
|
+
- **Social Links**: Add one or more social profiles
|
|
85
|
+
- **Tags**
|
|
82
86
|
|
|
83
87
|
### Create Company
|
|
84
88
|
|
|
@@ -103,6 +107,7 @@ Webhook → IDB2B Create Contact
|
|
|
103
107
|
- Name: {{ $json.name }}
|
|
104
108
|
- Email: {{ $json.email }}
|
|
105
109
|
- Phone: {{ $json.phone }}
|
|
110
|
+
- Additional Fields.LinkedIn URL: {{ $json.linkedin_url }}
|
|
106
111
|
```
|
|
107
112
|
|
|
108
113
|
### Paginate through all contacts
|
|
@@ -58,7 +58,6 @@ class IDB2B {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
async execute() {
|
|
61
|
-
var _a;
|
|
62
61
|
const items = this.getInputData();
|
|
63
62
|
const returnData = [];
|
|
64
63
|
const errorHandler = errorHandler_1.defaultErrorHandler;
|
|
@@ -104,17 +103,16 @@ class IDB2B {
|
|
|
104
103
|
method = "POST";
|
|
105
104
|
endpoint = constants_1.ENDPOINTS.CONTACTS;
|
|
106
105
|
const name = this.getNodeParameter("name", i);
|
|
107
|
-
const email = this.getNodeParameter("email", i
|
|
108
|
-
const phone_number = this.getNodeParameter("phone_number", i, "")
|
|
106
|
+
const email = this.getNodeParameter("email", i);
|
|
107
|
+
const phone_number = this.getNodeParameter("phone_number", i, "");
|
|
109
108
|
const additionalFields = this.getNodeParameter("additionalFields", i, {});
|
|
110
109
|
const validation = validator.validateContactData(name, email, phone_number, false);
|
|
111
110
|
if (!validation.isValid) {
|
|
112
111
|
throw new Error(validation.error);
|
|
113
112
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
body = (0, common_1.buildContactRequestBody)(contactData);
|
|
113
|
+
body = (0, common_1.buildContactRequestBody)(Object.assign({ name,
|
|
114
|
+
email,
|
|
115
|
+
phone_number }, additionalFields));
|
|
118
116
|
initialBody = body;
|
|
119
117
|
}
|
|
120
118
|
else if (operation === "update") {
|
|
@@ -251,17 +249,9 @@ class IDB2B {
|
|
|
251
249
|
});
|
|
252
250
|
continue;
|
|
253
251
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
((_a = error.response) === null || _a === void 0 ? void 0 : _a.data)) {
|
|
258
|
-
try {
|
|
259
|
-
const apiError = error.response.data;
|
|
260
|
-
errorMsg += ` | API response: ${typeof apiError === "string" ? apiError : JSON.stringify(apiError)}`;
|
|
261
|
-
}
|
|
262
|
-
catch (_b) { }
|
|
263
|
-
}
|
|
264
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), new Error(errorMsg), { itemIndex: i });
|
|
252
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, {
|
|
253
|
+
itemIndex: i,
|
|
254
|
+
});
|
|
265
255
|
}
|
|
266
256
|
}
|
|
267
257
|
return [returnData];
|
|
@@ -191,6 +191,13 @@ exports.contactFields = [
|
|
|
191
191
|
default: '',
|
|
192
192
|
description: 'UUID of the user who owns this contact',
|
|
193
193
|
},
|
|
194
|
+
{
|
|
195
|
+
displayName: 'LinkedIn URL',
|
|
196
|
+
name: 'linkedin_url',
|
|
197
|
+
type: 'string',
|
|
198
|
+
default: '',
|
|
199
|
+
description: 'LinkedIn profile URL to attach as a social link',
|
|
200
|
+
},
|
|
194
201
|
{
|
|
195
202
|
displayName: 'Tags',
|
|
196
203
|
name: 'tags',
|
|
@@ -217,6 +224,56 @@ exports.contactFields = [
|
|
|
217
224
|
},
|
|
218
225
|
],
|
|
219
226
|
},
|
|
227
|
+
{
|
|
228
|
+
displayName: 'Social Links',
|
|
229
|
+
name: 'socials',
|
|
230
|
+
type: 'fixedCollection',
|
|
231
|
+
typeOptions: {
|
|
232
|
+
multipleValues: true,
|
|
233
|
+
},
|
|
234
|
+
default: {},
|
|
235
|
+
placeholder: 'Add Social Link',
|
|
236
|
+
description: 'Social links to associate with the contact',
|
|
237
|
+
options: [
|
|
238
|
+
{
|
|
239
|
+
name: 'social',
|
|
240
|
+
displayName: 'Social',
|
|
241
|
+
values: [
|
|
242
|
+
{
|
|
243
|
+
displayName: 'Platform',
|
|
244
|
+
name: 'social_type',
|
|
245
|
+
type: 'options',
|
|
246
|
+
default: 'linkedin',
|
|
247
|
+
options: [
|
|
248
|
+
{ name: 'LinkedIn', value: 'linkedin' },
|
|
249
|
+
{ name: 'Instagram', value: 'instagram' },
|
|
250
|
+
{ name: 'Facebook', value: 'facebook' },
|
|
251
|
+
{ name: 'Twitter / X', value: 'twitter' },
|
|
252
|
+
{ name: 'GitHub', value: 'github' },
|
|
253
|
+
{ name: 'YouTube', value: 'youtube' },
|
|
254
|
+
{ name: 'TikTok', value: 'tiktok' },
|
|
255
|
+
{ name: 'Website', value: 'website' },
|
|
256
|
+
],
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
displayName: 'Value',
|
|
260
|
+
name: 'social_identifier',
|
|
261
|
+
type: 'string',
|
|
262
|
+
default: '',
|
|
263
|
+
required: true,
|
|
264
|
+
description: 'Username, slug, or unique social identifier',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
displayName: 'Social URL',
|
|
268
|
+
name: 'social_id',
|
|
269
|
+
type: 'string',
|
|
270
|
+
default: '',
|
|
271
|
+
description: 'Optional full URL. If empty, the node will derive it from the identifier when possible.',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
},
|
|
220
277
|
],
|
|
221
278
|
},
|
|
222
279
|
{
|
|
@@ -58,7 +58,7 @@ class ContactHandler {
|
|
|
58
58
|
*/
|
|
59
59
|
async create(params) {
|
|
60
60
|
// Validate required fields
|
|
61
|
-
const validation = this.validator.validateContactData(params.data.name, params.data.email, params.data.phone_number
|
|
61
|
+
const validation = this.validator.validateContactData(params.data.name, params.data.email, params.data.phone_number);
|
|
62
62
|
if (!validation.isValid) {
|
|
63
63
|
throw new Error(validation.error);
|
|
64
64
|
}
|
|
@@ -90,6 +90,14 @@ function processResponse(response, operation, requestBody) {
|
|
|
90
90
|
requestBody) {
|
|
91
91
|
processed = Object.assign(Object.assign({}, response), { data: Object.assign(Object.assign({}, requestBody), { created: true, status: "success", _note: "Server did not return the created entity. Fields like id and timestamps are unavailable." }) });
|
|
92
92
|
}
|
|
93
|
+
// Merge socials from request body into create/update response data
|
|
94
|
+
// The API does not return socials in the response, so we add them back
|
|
95
|
+
if ((operation === "create" || operation === "update") &&
|
|
96
|
+
response.message === "success" &&
|
|
97
|
+
response.data &&
|
|
98
|
+
(requestBody === null || requestBody === void 0 ? void 0 : requestBody.socials)) {
|
|
99
|
+
processed = Object.assign(Object.assign({}, processed), { data: Object.assign(Object.assign({}, processed.data), { socials: requestBody.socials }) });
|
|
100
|
+
}
|
|
93
101
|
// Standardize delete operation response
|
|
94
102
|
if (operation === "delete") {
|
|
95
103
|
processed = { deleted: true };
|
|
@@ -112,7 +120,33 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
112
120
|
"status_id",
|
|
113
121
|
"source_id",
|
|
114
122
|
"owner_id",
|
|
123
|
+
"socials",
|
|
115
124
|
]);
|
|
125
|
+
const buildSocialId = (socialType, socialIdentifier) => {
|
|
126
|
+
const trimmedIdentifier = socialIdentifier.trim();
|
|
127
|
+
const normalizedType = socialType.trim().toLowerCase();
|
|
128
|
+
if (trimmedIdentifier.startsWith("http://") ||
|
|
129
|
+
trimmedIdentifier.startsWith("https://")) {
|
|
130
|
+
return trimmedIdentifier;
|
|
131
|
+
}
|
|
132
|
+
if (normalizedType === "linkedin") {
|
|
133
|
+
return `https://www.linkedin.com/in/${trimmedIdentifier}`;
|
|
134
|
+
}
|
|
135
|
+
return trimmedIdentifier;
|
|
136
|
+
};
|
|
137
|
+
const extractLinkedInIdentifier = (value) => {
|
|
138
|
+
const trimmedValue = value.trim();
|
|
139
|
+
try {
|
|
140
|
+
const url = new URL(trimmedValue);
|
|
141
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
142
|
+
const profileIndex = parts.findIndex((part) => ["in", "company"].includes(part));
|
|
143
|
+
const candidate = profileIndex >= 0 ? parts[profileIndex + 1] : parts[parts.length - 1];
|
|
144
|
+
return candidate || trimmedValue;
|
|
145
|
+
}
|
|
146
|
+
catch (_a) {
|
|
147
|
+
return trimmedValue.replace(/^@/, "");
|
|
148
|
+
}
|
|
149
|
+
};
|
|
116
150
|
// Always include name and email if provided
|
|
117
151
|
if (data.name !== undefined) {
|
|
118
152
|
body.name = typeof data.name === "string" ? data.name.trim() : data.name;
|
|
@@ -126,6 +160,9 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
126
160
|
if (includesPhone) {
|
|
127
161
|
body.phone_number = (data.phone_number != null && data.phone_number !== "") ? String(data.phone_number) : "";
|
|
128
162
|
}
|
|
163
|
+
if (includesPhone && data.phone_number === "") {
|
|
164
|
+
body.phone_number = "";
|
|
165
|
+
}
|
|
129
166
|
// Handle tags specially - convert from fixedCollection format
|
|
130
167
|
if (data.tags && data.tags.tag && Array.isArray(data.tags.tag)) {
|
|
131
168
|
body.tags = data.tags.tag.map((tag) => ({
|
|
@@ -139,6 +176,39 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
139
176
|
name: typeof tag.name === "string" ? tag.name.trim() : tag.name,
|
|
140
177
|
}));
|
|
141
178
|
}
|
|
179
|
+
const socials = [];
|
|
180
|
+
if (typeof data.linkedin_url === "string" && data.linkedin_url.trim()) {
|
|
181
|
+
const socialId = data.linkedin_url.trim();
|
|
182
|
+
socials.push({
|
|
183
|
+
social_type: "linkedin",
|
|
184
|
+
social_id: socialId,
|
|
185
|
+
social_identifier: extractLinkedInIdentifier(socialId),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (data.socials && data.socials.social && Array.isArray(data.socials.social)) {
|
|
189
|
+
data.socials.social.forEach((social) => {
|
|
190
|
+
if (!(social === null || social === void 0 ? void 0 : social.social_type) || !(social === null || social === void 0 ? void 0 : social.social_identifier)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const socialType = typeof social.social_type === "string"
|
|
194
|
+
? social.social_type.trim().toLowerCase()
|
|
195
|
+
: social.social_type;
|
|
196
|
+
const socialIdentifier = typeof social.social_identifier === "string"
|
|
197
|
+
? social.social_identifier.trim()
|
|
198
|
+
: social.social_identifier;
|
|
199
|
+
const providedSocialId = typeof social.social_id === "string" ? social.social_id.trim() : "";
|
|
200
|
+
socials.push({
|
|
201
|
+
social_type: socialType,
|
|
202
|
+
social_id: providedSocialId || buildSocialId(String(socialType), String(socialIdentifier)),
|
|
203
|
+
social_identifier: socialType === "linkedin"
|
|
204
|
+
? extractLinkedInIdentifier(String(socialIdentifier))
|
|
205
|
+
: socialIdentifier,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (socials.length > 0) {
|
|
210
|
+
body.socials = socials;
|
|
211
|
+
}
|
|
142
212
|
Object.entries(data).forEach(([rawKey, rawValue]) => {
|
|
143
213
|
var _a;
|
|
144
214
|
if ([
|
|
@@ -146,6 +216,8 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
146
216
|
"email",
|
|
147
217
|
"phone_number",
|
|
148
218
|
"tags",
|
|
219
|
+
"linkedin_url",
|
|
220
|
+
"socials",
|
|
149
221
|
].includes(rawKey)) {
|
|
150
222
|
return;
|
|
151
223
|
}
|
|
@@ -60,9 +60,9 @@ class DataValidator {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
if (requirePhone &&
|
|
63
|
-
(
|
|
64
|
-
|
|
65
|
-
phoneNumber
|
|
63
|
+
(phoneNumber === undefined ||
|
|
64
|
+
phoneNumber === null ||
|
|
65
|
+
typeof phoneNumber !== "string")) {
|
|
66
66
|
return {
|
|
67
67
|
isValid: false,
|
|
68
68
|
error: "Contact phone number is required",
|