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 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,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
- const contactData = Object.assign({ name }, additionalFields);
115
- if (email)
116
- contactData.email = email;
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
- let errorMsg = error.message;
257
- if (error instanceof Error &&
258
- "response" in error &&
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, 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;
@@ -122,12 +148,12 @@ function buildContactRequestBody(data, includesPhone = true) {
122
148
  if (trimmedEmail !== "")
123
149
  body.email = trimmedEmail;
124
150
  }
125
- // Include phone if provided
126
- if (includesPhone &&
127
- data.phone_number !== undefined &&
128
- data.phone_number !== null &&
129
- data.phone_number !== "") {
130
- body.phone_number = data.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
- (!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.0",
3
+ "version": "3.2.3",
4
4
  "description": "n8n community node for IDB2B - WhatsApp AI Agents",
5
5
  "main": "index.js",
6
6
  "scripts": {