n8n-nodes-idb2b 3.2.1 → 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 -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 +64 -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
|
}
|
|
@@ -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;
|
|
@@ -126,6 +152,9 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
126
152
|
if (includesPhone) {
|
|
127
153
|
body.phone_number = (data.phone_number != null && data.phone_number !== "") ? String(data.phone_number) : "";
|
|
128
154
|
}
|
|
155
|
+
if (includesPhone && data.phone_number === "") {
|
|
156
|
+
body.phone_number = "";
|
|
157
|
+
}
|
|
129
158
|
// Handle tags specially - convert from fixedCollection format
|
|
130
159
|
if (data.tags && data.tags.tag && Array.isArray(data.tags.tag)) {
|
|
131
160
|
body.tags = data.tags.tag.map((tag) => ({
|
|
@@ -139,6 +168,39 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
139
168
|
name: typeof tag.name === "string" ? tag.name.trim() : tag.name,
|
|
140
169
|
}));
|
|
141
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
|
+
}
|
|
142
204
|
Object.entries(data).forEach(([rawKey, rawValue]) => {
|
|
143
205
|
var _a;
|
|
144
206
|
if ([
|
|
@@ -146,6 +208,8 @@ function buildContactRequestBody(data, includesPhone = true) {
|
|
|
146
208
|
"email",
|
|
147
209
|
"phone_number",
|
|
148
210
|
"tags",
|
|
211
|
+
"linkedin_url",
|
|
212
|
+
"socials",
|
|
149
213
|
].includes(rawKey)) {
|
|
150
214
|
return;
|
|
151
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",
|