Skip to content

Commit 9afc05a

Browse files
committed
Updates to GTM Template- address review comments
Replaced "let" to "Var" Formatted code, removed extra white space added documentation Tested to make sure that backward compatibility is preserved.
1 parent 526f223 commit 9afc05a

File tree

1 file changed

+52
-71
lines changed

1 file changed

+52
-71
lines changed

template.tpl

Lines changed: 52 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ ___SANDBOXED_JS_FOR_SERVER___
210210
// LinkedIn Conversion API documentation: https://learn.microsoft.com/en-us/linkedin/marketing/integrations/ads-reporting/conversions-api
211211

212212
// Sandbox Javascript API imports
213+
// Required GTM Server-Side functions/modules
213214
const sendHttpRequest = require('sendHttpRequest');
214215
const getTimestampMillis = require('getTimestampMillis');
215216
const getAllEventData = require('getAllEventData');
@@ -224,41 +225,39 @@ const getType = require('getType');
224225
const encodeUriComponent = require('encodeUriComponent');
225226
const makeTableMap = require('makeTableMap');
226227

227-
// ** CONSTANTS **
228+
// Constants
228229
const CONV_API_ENDPOINT = "https://api.linkedin.com/rest/conversionEvents/";
230+
const linkedin_api_version = '202503'; // LinkedIn API version (valid for 1 year from release)
229231

230-
// API_version Indicates the API version that is being used. Each version is supported for 1 year following it's release.
231-
const linkedin_api_version = '202410';
232-
233-
// Dynamic variables
232+
// Input / Event Data Setup
234233
const eventModel = getAllEventData();
235234
const user_data = eventModel.user_data || {};
236235
const user_address = user_data.address || {};
237236
const eventDataOverride = makeOverrideTableMap(data.eventData);
238237
const userIdsOverride = makeOverrideTableMap(data.userIds);
239238
const userInfoOverride = makeOverrideTableMap(data.userInfo);
240239

240+
// Conversion Rule URN Construction
241241
const conversion_rule_id = eventModel.conversion_rule_id || data.conversionRuleUrn;
242-
243242
const conversion_rule_urn = 'urn:lla:llaPartnerConversion:' + conversion_rule_id;
244243

245-
// Build conversion_event object
244+
// Build conversion event payload
245+
//Check if the conversion Happened time is provided by the user, if not then use the current timestamp.
246246
const conversion_event = {
247247
conversion_happened_at: makeNumber(eventModel.conversion_happened_at || eventModel.event_time) || Math.round(getTimestampMillis()),
248248
userData: {
249249
userIds: getUserIds()
250250
}
251251
};
252252

253-
// Add lead ID if available
254-
// Add lead ID if set in user_data
253+
// Add LinkedIn LeadGen lead ID if available
255254
if (eventModel.user_data != null && eventModel.user_data.leadID != null) {
256-
const leadId = eventModel.user_data.leadID;
257-
const leadIdUrn = (leadId !== "") ? 'urn:li:leadGenFormResponse:' + leadId : '';
258-
conversion_event.userData.lead = leadIdUrn;
255+
const leadId = eventModel.user_data.leadID;
256+
const leadIdUrn = leadId !== "" ? 'urn:li:leadGenFormResponse:' + leadId : '';
257+
conversion_event.userData.lead = leadIdUrn;
259258
}
260259

261-
// Fetch event ID and conversion values
260+
// Conversion metadata
262261
const eventID = eventDataOverride.eventId || data.eventId || eventModel.eventId || eventModel.event_id || '';
263262
const conversionCurrency = eventDataOverride.currency || eventModel.currency || '';
264263
const conversionAmount = eventDataOverride.amount || eventModel.value || '0';
@@ -267,67 +266,66 @@ const conversionValue = JSON.parse(data.conversionValue) || {
267266
amount: conversionAmount.toString()
268267
};
269268

270-
// Include user information if valid
271-
269+
// Add user info if available
272270
const userDataInfo = getUserInfo();
273271
if (userDataInfo && userDataInfo.firstName && userDataInfo.lastName) {
274-
conversion_event.userData.userInfo = userDataInfo;
272+
conversion_event.userData.userInfo = userDataInfo;
275273
}
276274

277-
// Preparing the HTTP POST call to LinkedIn CAPI endpoint
275+
// HTTP request headers
278276
const requestHeaders = {
279277
'content-type': 'application/json',
280278
'Authorization': 'Bearer ' + data.apiAccessToken,
281279
'LinkedIn-Version': linkedin_api_version
282280
};
283281

284-
var postBody = {
282+
// Construct request payload
283+
let postBody = {
285284
conversion: conversion_rule_urn,
286285
conversionHappenedAt: conversion_event.conversion_happened_at,
287286
eventId: eventID,
288287
user: conversion_event.userData
289288
};
289+
290290
if (conversionValue.currencyCode !== "" && conversionValue.amount > 0) {
291291
postBody.conversionValue = conversionValue;
292292
}
293293

294-
// perform validation check on presence of 1/4 of the required IDs. If at least 1 ID is present, make the API call. If no IDs are present, log the warning and no call is made
294+
// Main validation + send logic
295295
if (validateUserData()) {
296296
sendConversionToLinkedIn();
297297
} else {
298298
logToConsole('No conversion event was sent to CAPI. You must set 1 out of the 4 acceptable IDs (Acxiom, Oracle, SHA256_Email, or LinkedIn_UUID) or provide firstName & lastName under eventModel.user_data.address.');
299299
}
300300

301+
// Validates presence of at least one required ID or name
301302
function validateUserData() {
302-
var data_is_valid_flag = false;
303+
let data_is_valid_flag = false;
303304
const userIDs = conversion_event.userData.userIds;
304305
305-
for (var user_id of userIDs) {
306-
if (user_id.idValue != "") {
307-
// means that the idValue is present for one of the required ID feilds
306+
for (let user_id of userIDs) {
307+
if (user_id.idValue !== "") {
308308
data_is_valid_flag = true;
309309
break;
310310
}
311311
}
312-
// if the flag is false, check for first name last name. if both present,
313-
// flip flag to true, send the API call
314-
if (data_is_valid_flag == false) {
315-
if (
316-
conversion_event.userData &&
317-
conversion_event.userData.userInfo &&
318-
conversion_event.userData.userInfo.firstName &&
319-
conversion_event.userData.userInfo.lastName
320-
) {
312+
313+
if (!data_is_valid_flag) {
314+
const userInfo = conversion_event.userData.userInfo;
315+
if (userInfo && userInfo.firstName && userInfo.lastName) {
321316
data_is_valid_flag = true;
322317
}
323318
}
319+
324320
return data_is_valid_flag;
325321
}
326322

323+
// Converts array of overrides into map
327324
function makeOverrideTableMap(values) {
328325
return makeTableMap(values || [], 'name', 'value') || {};
329326
}
330327

328+
// Gathers available user IDs
331329
function getUserIds() {
332330
const userIds = [{
333331
idType: 'SHA256_EMAIL',
@@ -347,12 +345,13 @@ function getUserIds() {
347345
}
348346
];
349347

350-
return userIds.filter((userId) => userId.idValue);
348+
return userIds.filter(userId => userId.idValue);
351349
}
352350

351+
// Gets email, prioritizing hashed version
353352
function getUserEmail() {
354353
const hashedEmail = getUserDataHashedEmail();
355-
if (hashedEmail) return hashedEmail; // Prioritize hashed email if available
354+
if (hashedEmail) return hashedEmail;
356355
357356
return (
358357
(userIdsOverride.email ||
@@ -376,24 +375,21 @@ function getOracleMoatId() {
376375
return userIdsOverride.moatID || user_data.moatID || '';
377376
}
378377

378+
// Gets LinkedIn first-party UUID
379379
function getFirstPartyAdsTrackingUuid() {
380-
// Check if eventModel.user_data exists before accessing its properties
381380
const eventLinkedinFirstPartyID =
382381
eventModel.user_data && eventModel.user_data.linkedinFirstPartyId ? eventModel.user_data.linkedinFirstPartyId : '';
383-
384-
// Fallback: Try fetching from user properties
385382
const ga4UserPropPrefix = 'x-ga-mp2-user_properties.';
386383
const userLinkedinFirstPartyID = getEventData(ga4UserPropPrefix + 'linkedinFirstPartyId') || '';
387-
388-
// Return the first available ID
389384
return eventLinkedinFirstPartyID || userLinkedinFirstPartyID || '';
390385
}
391386

387+
// Fallback lookup for user information
392388
function getUserFirstName() {
393389
return (
394390
userInfoOverride.firstName ||
395391
eventModel.firstName ||
396-
eventModel.FirstName || // Handles possible inconsistent casing
392+
eventModel.FirstName ||
397393
eventModel.nameFirst ||
398394
eventModel.first_name ||
399395
user_data.first_name ||
@@ -406,7 +402,7 @@ function getUserLastName() {
406402
return (
407403
userInfoOverride.lastName ||
408404
eventModel.lastName ||
409-
eventModel.LastName || // Handles possible inconsistent casing
405+
eventModel.LastName ||
410406
eventModel.nameLast ||
411407
eventModel.last_name ||
412408
user_data.last_name ||
@@ -418,7 +414,7 @@ function getUserLastName() {
418414
function getUserJobTitle() {
419415
return (
420416
userInfoOverride.jobTitle ||
421-
eventModel.jobTitle || // Handles possible inconsistent casing
417+
eventModel.jobTitle ||
422418
user_data.jobTitle ||
423419
user_data.job_title ||
424420
''
@@ -428,7 +424,7 @@ function getUserJobTitle() {
428424
function getUserCompanyName() {
429425
return (
430426
userInfoOverride.companyName ||
431-
eventModel.companyName || // Handles possible inconsistent casing
427+
eventModel.companyName ||
432428
eventModel.company_name ||
433429
user_data.companyName ||
434430
user_data.company_name ||
@@ -439,14 +435,15 @@ function getUserCompanyName() {
439435
function getUserCountryCode() {
440436
return (
441437
userInfoOverride.countryCode ||
442-
eventModel.countryCode || // Handles possible inconsistent casing
438+
eventModel.countryCode ||
443439
eventModel.country ||
444440
user_data.country ||
445441
user_address.country ||
446442
''
447443
);
448444
}
449445

446+
// Builds userInfo object
450447
function getUserInfo() {
451448
return {
452449
firstName: getUserFirstName(),
@@ -457,55 +454,38 @@ function getUserInfo() {
457454
};
458455
}
459456

457+
// Check if a value is already a valid SHA256 hash
460458
function isHashed(value) {
461-
if (!value) {
462-
return false;
463-
}
464-
459+
if (!value) return false;
465460
return makeString(value).match('^[A-Fa-f0-9]{64}$') !== null;
466461
}
467462

463+
// Hashes non-object and non-array string values if not already hashed
468464
function hashData(value) {
469-
if (!value) {
470-
return value;
471-
}
465+
if (!value) return value;
472466
473467
const type = getType(value);
474-
475-
if (type === 'undefined' || value === 'undefined') {
476-
return undefined;
477-
}
478-
479-
if (type === 'object' || type === 'array') {
480-
return value;
481-
}
482-
483-
if (isHashed(value)) {
484-
return value;
485-
}
468+
if (type === 'undefined' || value === 'undefined') return undefined;
469+
if (type === 'object' || type === 'array') return undefined;
470+
if (isHashed(value)) return value;
486471
487472
value = makeString(value).trim().toLowerCase();
488-
489473
return sha256Sync(value, {
490474
outputEncoding: 'hex'
491475
});
492476
}
493477

478+
// Encodes a string for URI safety
494479
function enc(data) {
495480
return encodeUriComponent(data || '');
496481
}
497482

498-
function makeOverrideTableMap(values) {
499-
return makeTableMap(values || [], 'name', 'value') || {};
500-
}
501-
483+
// Send the API request to LinkedIn
502484
function sendConversionToLinkedIn() {
503-
// Sending the API Call
504485
sendHttpRequest(CONV_API_ENDPOINT, {
505486
headers: requestHeaders,
506487
method: 'POST',
507-
//timeout: 500,
508-
}, JSON.stringify(postBody)).then((result) => {
488+
}, JSON.stringify(postBody)).then(result => {
509489
if (result.statusCode >= 200 && result.statusCode < 300) {
510490
data.gtmOnSuccess();
511491
} else {
@@ -514,6 +494,7 @@ function sendConversionToLinkedIn() {
514494
});
515495
}
516496

497+
517498
___SERVER_PERMISSIONS___
518499

519500
[

0 commit comments

Comments
 (0)