n8n-nodes-idb2b 3.2.0 → 3.2.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/README.md +7 -2
- package/dist/nodes/IDB2B/IDB2B.node.js +8 -20
- 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 +67 -6
- 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,19 +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
|
-
if (phone_number)
|
|
118
|
-
contactData.phone_number = phone_number;
|
|
119
|
-
body = (0, common_1.buildContactRequestBody)(contactData);
|
|
113
|
+
body = (0, common_1.buildContactRequestBody)(Object.assign({ name,
|
|
114
|
+
email,
|
|
115
|
+
phone_number }, additionalFields));
|
|
120
116
|
initialBody = body;
|
|
121
117
|
}
|
|
122
118
|
else if (operation === "update") {
|
|
@@ -253,17 +249,9 @@ class IDB2B {
|
|
|
253
249
|
});
|
|
254
250
|
continue;
|
|
255
251
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
((_a = error.response) === null || _a === void 0 ? void 0 : _a.data)) {
|
|
260
|
-
try {
|
|
261
|
-
const apiError = error.response.data;
|
|
262
|
-
errorMsg += ` | API response: ${typeof apiError === "string" ? apiError : JSON.stringify(apiError)}`;
|
|
263
|
-
}
|
|
264
|
-
catch (_b) { }
|
|
265
|
-
}
|
|
266
|
-
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
|
+
});
|
|
267
255
|
}
|
|
268
256
|
}
|
|
269
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
|
}
|
|
@@ -112,7 +112,33 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
112
112
|
"status_id",
|
|
113
113
|
"source_id",
|
|
114
114
|
"owner_id",
|
|
115
|
+
"socials",
|
|
115
116
|
]);
|
|
117
|
+
const buildSocialId = (socialType, socialIdentifier) => {
|
|
118
|
+
const trimmedIdentifier = socialIdentifier.trim();
|
|
119
|
+
const normalizedType = socialType.trim().toLowerCase();
|
|
120
|
+
if (trimmedIdentifier.startsWith("http://") ||
|
|
121
|
+
trimmedIdentifier.startsWith("https://")) {
|
|
122
|
+
return trimmedIdentifier;
|
|
123
|
+
}
|
|
124
|
+
if (normalizedType === "linkedin") {
|
|
125
|
+
return `https://www.linkedin.com/in/${trimmedIdentifier}`;
|
|
126
|
+
}
|
|
127
|
+
return trimmedIdentifier;
|
|
128
|
+
};
|
|
129
|
+
const extractLinkedInIdentifier = (value) => {
|
|
130
|
+
const trimmedValue = value.trim();
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(trimmedValue);
|
|
133
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
134
|
+
const profileIndex = parts.findIndex((part) => ["in", "company"].includes(part));
|
|
135
|
+
const candidate = profileIndex >= 0 ? parts[profileIndex + 1] : parts[parts.length - 1];
|
|
136
|
+
return candidate || trimmedValue;
|
|
137
|
+
}
|
|
138
|
+
catch (_a) {
|
|
139
|
+
return trimmedValue.replace(/^@/, "");
|
|
140
|
+
}
|
|
141
|
+
};
|
|
116
142
|
// Always include name and email if provided
|
|
117
143
|
if (data.name !== undefined) {
|
|
118
144
|
body.name = typeof data.name === "string" ? data.name.trim() : data.name;
|
|
@@ -122,12 +148,12 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
122
148
|
if (trimmedEmail !== "")
|
|
123
149
|
body.email = trimmedEmail;
|
|
124
150
|
}
|
|
125
|
-
//
|
|
126
|
-
if (includesPhone
|
|
127
|
-
data.phone_number !==
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
body.phone_number =
|
|
151
|
+
// Always include phone_number as a string (API requires it)
|
|
152
|
+
if (includesPhone) {
|
|
153
|
+
body.phone_number = (data.phone_number != null && data.phone_number !== "") ? String(data.phone_number) : "";
|
|
154
|
+
}
|
|
155
|
+
if (includesPhone && data.phone_number === "") {
|
|
156
|
+
body.phone_number = "";
|
|
131
157
|
}
|
|
132
158
|
// Handle tags specially - convert from fixedCollection format
|
|
133
159
|
if (data.tags && data.tags.tag && Array.isArray(data.tags.tag)) {
|
|
@@ -142,6 +168,39 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
142
168
|
name: typeof tag.name === "string" ? tag.name.trim() : tag.name,
|
|
143
169
|
}));
|
|
144
170
|
}
|
|
171
|
+
const socials = [];
|
|
172
|
+
if (typeof data.linkedin_url === "string" && data.linkedin_url.trim()) {
|
|
173
|
+
const socialId = data.linkedin_url.trim();
|
|
174
|
+
socials.push({
|
|
175
|
+
social_type: "linkedin",
|
|
176
|
+
social_id: socialId,
|
|
177
|
+
social_identifier: extractLinkedInIdentifier(socialId),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (data.socials && data.socials.social && Array.isArray(data.socials.social)) {
|
|
181
|
+
data.socials.social.forEach((social) => {
|
|
182
|
+
if (!(social === null || social === void 0 ? void 0 : social.social_type) || !(social === null || social === void 0 ? void 0 : social.social_identifier)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const socialType = typeof social.social_type === "string"
|
|
186
|
+
? social.social_type.trim().toLowerCase()
|
|
187
|
+
: social.social_type;
|
|
188
|
+
const socialIdentifier = typeof social.social_identifier === "string"
|
|
189
|
+
? social.social_identifier.trim()
|
|
190
|
+
: social.social_identifier;
|
|
191
|
+
const providedSocialId = typeof social.social_id === "string" ? social.social_id.trim() : "";
|
|
192
|
+
socials.push({
|
|
193
|
+
social_type: socialType,
|
|
194
|
+
social_id: providedSocialId || buildSocialId(String(socialType), String(socialIdentifier)),
|
|
195
|
+
social_identifier: socialType === "linkedin"
|
|
196
|
+
? extractLinkedInIdentifier(String(socialIdentifier))
|
|
197
|
+
: socialIdentifier,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (socials.length > 0) {
|
|
202
|
+
body.socials = socials;
|
|
203
|
+
}
|
|
145
204
|
Object.entries(data).forEach(([rawKey, rawValue]) => {
|
|
146
205
|
var _a;
|
|
147
206
|
if ([
|
|
@@ -149,6 +208,8 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
149
208
|
"email",
|
|
150
209
|
"phone_number",
|
|
151
210
|
"tags",
|
|
211
|
+
"linkedin_url",
|
|
212
|
+
"socials",
|
|
152
213
|
].includes(rawKey)) {
|
|
153
214
|
return;
|
|
154
215
|
}
|
|
@@ -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",
|