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 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, User ID, Lead ID, Favorites, Tags
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
- const contactData = Object.assign({ name, phone_number: phone_number || "" }, additionalFields);
115
- if (email)
116
- contactData.email = email;
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
- let errorMsg = error.message;
255
- if (error instanceof Error &&
256
- "response" in error &&
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, false);
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
- (!phoneNumber ||
64
- typeof phoneNumber !== "string" ||
65
- phoneNumber.trim().length === 0)) {
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-idb2b",
3
- "version": "3.2.1",
3
+ "version": "3.2.3",
4
4
  "description": "n8n community node for IDB2B - WhatsApp AI Agents",
5
5
  "main": "index.js",
6
6
  "scripts": {