@getuserfeedback/adapters 0.4.5 → 0.5.1

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.
@@ -1,31 +1,37 @@
1
- import { DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS, } from "@getuserfeedback/protocol";
2
- import { DEFAULT_GTM_SCOPE_MAPPINGS, ESSENTIAL_GTM_SCOPES, GTM_CONSENT_TYPES, GTM_TEMPLATE_MAPPING_OPTIONS, GTM_TEMPLATE_SCOPE_OPTIONS, isHostConfigurableGtmScope, } from "./core.js";
1
+ import { DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS } from "@getuserfeedback/protocol";
2
+ import { GTM_CONSENT_TYPES } from "./core.js";
3
+ import { BUILD_GTM_IDENTIFY_COMMAND_SOURCE, GTM_QUEUE_CLIENT_META, RESOLVE_GTM_THEME_INPUT_FROM_TEMPLATE_DATA_SOURCE, } from "./runtime-model.js";
3
4
  export const DEFAULT_GTM_LOADER_SCRIPT_BASE_URL = "https://cdn.getuserfeedback.com/widget/loader/v1";
4
5
  const GTM_TEMPLATE_VERSION = 1;
5
6
  const GTM_TEMPLATE_DEFAULT_DISPLAY_NAME = "getuserfeedback Widget";
6
- const GTM_TEMPLATE_DEFAULT_DESCRIPTION = "Load the getuserfeedback widget with Google Tag Manager, including theme settings and consent defaults.";
7
+ const GTM_TEMPLATE_DEFAULT_DESCRIPTION = "Load the getuserfeedback widget with Google Tag Manager, including color scheme settings and user identity.";
8
+ const GTM_TEMPLATE_DEFAULT_ID = "cvt_temp_public_id";
9
+ const GTM_TEMPLATE_GALLERY_DISPLAY_NAME = "getuserfeedback.com";
10
+ const GTM_TEMPLATE_GALLERY_DESCRIPTION = "In-app user onboarding, surveys, and embedded forms for feedback-fueled product growth";
11
+ const GTM_TEMPLATE_DEFAULT_BRAND_DISPLAY_NAME = "getuserfeedback.com";
12
+ const GTM_TEMPLATE_DEFAULT_BRAND_THUMBNAIL = "data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA=";
13
+ const GTM_TEMPLATE_GALLERY_CATEGORIES = [
14
+ "CONVERSIONS",
15
+ "ANALYTICS",
16
+ "SURVEY",
17
+ ];
7
18
  const GTM_TEMPLATE_CATEGORIES = [
8
19
  "SURVEY",
9
20
  "PERSONALIZATION",
10
21
  "UTILITY",
11
22
  ];
12
23
  const GTM_TEMPLATE_CONTAINER_CONTEXTS = ["WEB"];
24
+ const GTM_TEMPLATE_GALLERY_TERMS_OF_SERVICE = [
25
+ "By creating or modifying this file you agree to Google Tag Manager's Community",
26
+ "Template Gallery Developer Terms of Service available at",
27
+ "https://developers.google.com/tag-manager/gallery-tos (or such other URL as",
28
+ "Google may provide), as modified from time to time.",
29
+ ].join("\n");
30
+ const GTM_TEMPLATE_DEFAULT_NOTES = "Generated from @getuserfeedback/adapters/gtm.";
31
+ const GTM_TEMPLATE_DEFAULT_TESTS = "scenarios: []";
32
+ const GTM_TEMPLATE_UTF8_BOM = "\uFEFF";
13
33
  const GTM_QUEUE_KEY = "__getuserfeedback_queue";
14
34
  const NON_EMPTY_VALIDATOR = { type: "NON_EMPTY" };
15
- const MANUAL_CONSENT_MODE_CONDITION = [
16
- {
17
- paramName: "consentMode",
18
- paramValue: "manual",
19
- type: "EQUALS",
20
- },
21
- ];
22
- const AUTOMATIC_CONSENT_MODE_CONDITION = [
23
- {
24
- paramName: "consentMode",
25
- paramValue: "automatic",
26
- type: "EQUALS",
27
- },
28
- ];
29
35
  const IDENTIFY_ADVANCED_MODE_CONDITION = [
30
36
  {
31
37
  paramName: "identifyMode",
@@ -49,6 +55,42 @@ const INTELLIGENT_IDENTIFY_EMAIL_KEYS = [
49
55
  "context.email",
50
56
  "context.user.email",
51
57
  ];
58
+ const INTELLIGENT_IDENTIFY_FIRST_NAME_KEYS = [
59
+ "firstName",
60
+ "first_name",
61
+ "user.firstName",
62
+ "user.first_name",
63
+ "user.givenName",
64
+ "user.given_name",
65
+ "traits.firstName",
66
+ "traits.first_name",
67
+ "traits.givenName",
68
+ "traits.given_name",
69
+ "context.firstName",
70
+ "context.first_name",
71
+ "context.user.firstName",
72
+ "context.user.first_name",
73
+ "context.user.givenName",
74
+ "context.user.given_name",
75
+ ];
76
+ const INTELLIGENT_IDENTIFY_LAST_NAME_KEYS = [
77
+ "lastName",
78
+ "last_name",
79
+ "user.lastName",
80
+ "user.last_name",
81
+ "user.familyName",
82
+ "user.family_name",
83
+ "traits.lastName",
84
+ "traits.last_name",
85
+ "traits.familyName",
86
+ "traits.family_name",
87
+ "context.lastName",
88
+ "context.last_name",
89
+ "context.user.lastName",
90
+ "context.user.last_name",
91
+ "context.user.familyName",
92
+ "context.user.family_name",
93
+ ];
52
94
  const INTELLIGENT_IDENTIFY_PHONE_KEYS = [
53
95
  "phone",
54
96
  "user.phone",
@@ -70,31 +112,6 @@ const ADVANCED_PRIMARY_IDENTITY_SELECT_ITEMS = [
70
112
  value: "email",
71
113
  },
72
114
  ];
73
- const buildAutomaticDefaultScopeMappings = () => DEFAULT_GTM_SCOPE_MAPPINGS.filter((mapping) => isHostConfigurableGtmScope(mapping.scope)).map((mapping) => ({
74
- scope: mapping.scope,
75
- mapping: mapping.consentType,
76
- }));
77
- const buildHostConfigurableDefaultScopeMappings = () => [
78
- ...buildAutomaticDefaultScopeMappings(),
79
- {
80
- scope: "analytics.measurement",
81
- mapping: "analytics_storage",
82
- },
83
- ];
84
- const buildScopeParameterHelp = () => [
85
- "Some required widget storage settings are always enabled and do not appear here.",
86
- `${ESSENTIAL_GTM_SCOPES[0]} and ${ESSENTIAL_GTM_SCOPES[1]} are always enabled so the widget can load and keep itself secure.`,
87
- "",
88
- ...GTM_TEMPLATE_SCOPE_OPTIONS.filter((option) => isHostConfigurableGtmScope(option.scope)).map((option) => `${option.label}: ${option.description}`),
89
- ].join("\n");
90
- const buildHostConfigurableScopeSelectItems = () => GTM_TEMPLATE_SCOPE_OPTIONS.filter((option) => isHostConfigurableGtmScope(option.scope)).map((option) => ({
91
- value: option.scope,
92
- displayValue: option.label,
93
- }));
94
- const buildGtmConsentTypeSelectItems = () => GTM_TEMPLATE_MAPPING_OPTIONS.map((option) => ({
95
- value: option.consentType,
96
- displayValue: `${option.label} Granted`,
97
- }));
98
115
  const trimTrailingSlash = (value) => value.endsWith("/") ? value.slice(0, -1) : value;
99
116
  const stringValue = (value) => ({
100
117
  type: 1,
@@ -134,29 +151,61 @@ const buildPermission = (publicId, params) => ({
134
151
  },
135
152
  isRequired: true,
136
153
  });
154
+ const normalizeAdditionalLoaderScriptUrlPatterns = (patterns) => {
155
+ if (!patterns) {
156
+ return [];
157
+ }
158
+ const normalizedPatterns = new Set();
159
+ for (const pattern of patterns) {
160
+ const normalizedPattern = pattern.trim();
161
+ if (!normalizedPattern) {
162
+ continue;
163
+ }
164
+ normalizedPatterns.add(normalizedPattern);
165
+ }
166
+ return [...normalizedPatterns];
167
+ };
168
+ const renderTemplateSection = (marker, content) => `${marker}\n\n${content}`;
169
+ const normalizeTemplateStringSection = (value, fallback) => {
170
+ const normalizedValue = value?.trim();
171
+ return normalizedValue && normalizedValue.length > 0
172
+ ? normalizedValue
173
+ : fallback;
174
+ };
175
+ const normalizeTemplateStringArray = (value, fallback) => {
176
+ if (!value) {
177
+ return [...fallback];
178
+ }
179
+ const normalizedItems = [];
180
+ for (const item of value) {
181
+ const normalizedItem = item.trim();
182
+ if (normalizedItem.length === 0) {
183
+ continue;
184
+ }
185
+ normalizedItems.push(normalizedItem);
186
+ }
187
+ return normalizedItems.length > 0 ? normalizedItems : [...fallback];
188
+ };
137
189
  export function buildGtmTemplateParameters(_options) {
138
- const scopeHelp = buildScopeParameterHelp();
139
- const scopeSelectItems = buildHostConfigurableScopeSelectItems();
140
- const mappingSelectItems = buildGtmConsentTypeSelectItems();
141
190
  return [
142
191
  {
143
192
  type: "TEXT",
144
193
  name: "apiKey",
145
194
  displayName: "API Key",
146
195
  simpleValueType: true,
147
- help: "Find your API key in getuserfeedback.com under Settings → Widget.",
196
+ help: "Paste your getuserfeedback API key from Settings → Widget.",
148
197
  valueValidators: [NON_EMPTY_VALIDATOR],
149
198
  },
150
199
  {
151
200
  type: "GROUP",
152
201
  name: "themeGroup",
153
- displayName: "Theme Settings",
202
+ displayName: "Color Scheme",
154
203
  groupStyle: "ZIPPY_CLOSED",
155
204
  subParams: [
156
205
  {
157
206
  type: "SELECT",
158
207
  name: "themeMode",
159
- displayName: "Theme Mode",
208
+ displayName: "Color Scheme",
160
209
  macrosInSelect: false,
161
210
  selectItems: [
162
211
  {
@@ -182,16 +231,16 @@ export function buildGtmTemplateParameters(_options) {
182
231
  ],
183
232
  simpleValueType: true,
184
233
  defaultValue: "host",
185
- help: "Choose how the widget theme is set: use a fixed mode, follow your site's theme attributes, or read the value from a GTM variable.",
234
+ help: "Choose how the widget color scheme should work.",
186
235
  },
187
236
  {
188
237
  type: "SELECT",
189
238
  name: "themeModeVariable",
190
- displayName: "Theme Mode Variable",
239
+ displayName: "Color Scheme Variable",
191
240
  macrosInSelect: true,
192
241
  selectItems: [],
193
242
  simpleValueType: true,
194
- help: "Use a GTM variable that resolves to `light`, `dark`, `system`, `auto`, or a comma-separated list of attribute names.",
243
+ help: "Choose a GTM variable that returns `light`, `dark`, `system`, `auto`, or a comma-separated list of attribute names.",
195
244
  enablingConditions: [
196
245
  {
197
246
  paramName: "themeMode",
@@ -204,10 +253,10 @@ export function buildGtmTemplateParameters(_options) {
204
253
  {
205
254
  type: "TEXT",
206
255
  name: "themeAttr",
207
- displayName: "Host Theme Attributes",
256
+ displayName: "Site Color Scheme Attributes",
208
257
  simpleValueType: true,
209
258
  defaultValue: DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS.join(","),
210
- help: "Comma-separated attributes to inspect on `<html>` or `<body>` when matching the widget theme to your site.",
259
+ help: "Add the HTML or body attributes that show your site's color scheme, separated by commas.",
211
260
  enablingConditions: [
212
261
  {
213
262
  paramName: "themeMode",
@@ -218,93 +267,6 @@ export function buildGtmTemplateParameters(_options) {
218
267
  },
219
268
  ],
220
269
  },
221
- {
222
- type: "GROUP",
223
- name: "consentGroup",
224
- displayName: "Consent Settings",
225
- groupStyle: "ZIPPY_CLOSED",
226
- subParams: [
227
- {
228
- type: "RADIO",
229
- name: "consentMode",
230
- displayName: "Consent Mode",
231
- radioItems: [
232
- {
233
- value: "automatic",
234
- displayValue: "Automatic (Use GTM Consent Mode)",
235
- help: "Use GTM consent checks to set the widget's initial consent state. This also lets you decide whether analytics measurement should follow analytics storage or use its own fixed setting.",
236
- },
237
- {
238
- value: "manual",
239
- displayValue: "Manual (Map Public Scopes)",
240
- help: "Choose which widget consent settings are granted for each GTM consent type. Required widget storage settings stay enabled and are not configurable here.",
241
- },
242
- ],
243
- simpleValueType: true,
244
- defaultValue: "automatic",
245
- },
246
- {
247
- type: "SELECT",
248
- name: "analyticsMeasurementMode",
249
- displayName: "Analytics Measurement Policy",
250
- macrosInSelect: false,
251
- selectItems: [
252
- {
253
- value: "inherit-analytics-storage",
254
- displayValue: "Inherit analytics_storage",
255
- },
256
- {
257
- value: "granted",
258
- displayValue: "Always granted",
259
- },
260
- {
261
- value: "denied",
262
- displayValue: "Always denied",
263
- },
264
- ],
265
- simpleValueType: true,
266
- defaultValue: "inherit-analytics-storage",
267
- help: [
268
- "Analytics measurement is separate from analytics storage so you can control event measurement independently.",
269
- "Inherit analytics_storage: `analytics.measurement` matches GTM `analytics_storage`.",
270
- "Always granted: `analytics.measurement` is always enabled.",
271
- "Always denied: `analytics.measurement` is always disabled.",
272
- ].join("\n"),
273
- enablingConditions: [...AUTOMATIC_CONSENT_MODE_CONDITION],
274
- },
275
- {
276
- type: "SIMPLE_TABLE",
277
- name: "scopeMappings",
278
- displayName: "Scope Mappings",
279
- simpleTableColumns: [
280
- {
281
- defaultValue: "analytics.storage",
282
- displayName: "Widget Scope",
283
- name: "scope",
284
- type: "SELECT",
285
- isUnique: true,
286
- selectItems: scopeSelectItems,
287
- },
288
- {
289
- defaultValue: "analytics_storage",
290
- displayName: "Granted When",
291
- name: "mapping",
292
- type: "SELECT",
293
- macrosInSelect: true,
294
- selectItems: mappingSelectItems,
295
- },
296
- ],
297
- newRowButtonText: "Add Scope",
298
- help: scopeHelp,
299
- enablingConditions: [...MANUAL_CONSENT_MODE_CONDITION],
300
- valueValidators: [NON_EMPTY_VALIDATOR],
301
- defaultValue: buildHostConfigurableDefaultScopeMappings().map((row) => ({
302
- scope: row.scope,
303
- mapping: row.mapping,
304
- })),
305
- },
306
- ],
307
- },
308
270
  {
309
271
  type: "GROUP",
310
272
  name: "identifyGroup",
@@ -321,44 +283,44 @@ export function buildGtmTemplateParameters(_options) {
321
283
  {
322
284
  value: "intelligent",
323
285
  displayValue: "Intelligent",
324
- help: "Automatically read common data layer keys for user ID, email, and phone. User ID is sent as `identify(userId)` and email or phone stay as raw traits.",
286
+ help: "Automatically read common data layer keys for user ID, email, phone, first name, and last name.",
325
287
  },
326
288
  {
327
289
  value: "disabled",
328
290
  displayValue: "Disabled",
329
- help: "Do not send identify data from this GTM tag.",
291
+ help: "Don't send identity data from this tag.",
330
292
  },
331
293
  {
332
294
  value: "advanced",
333
- displayValue: "Advanced",
334
- help: "Choose how GTM encodes the primary identity and add any extra traits you want to send from GTM variables.",
295
+ displayValue: "Custom",
296
+ help: "Choose the main identity value and add any extra fields you want to send.",
335
297
  },
336
298
  ],
337
299
  },
338
300
  {
339
301
  type: "SELECT",
340
302
  name: "identifyPrimaryIdentityType",
341
- displayName: "Primary identity",
303
+ displayName: "Main identity",
342
304
  simpleValueType: true,
343
305
  macrosInSelect: false,
344
306
  defaultValue: "userId",
345
307
  selectItems: [...ADVANCED_PRIMARY_IDENTITY_SELECT_ITEMS],
346
- help: "Choose whether GTM should encode the primary identity as `identify(userId)` or as a raw identity trait like `email` or `phone`.",
308
+ help: "Choose whether the main identity should be sent as a user ID, email, or phone number.",
347
309
  enablingConditions: [...IDENTIFY_ADVANCED_MODE_CONDITION],
348
310
  },
349
311
  {
350
312
  type: "TEXT",
351
313
  name: "identifyPrimaryIdentityValue",
352
- displayName: "Primary identity value",
314
+ displayName: "Main identity value",
353
315
  simpleValueType: true,
354
- help: "Use a GTM variable that resolves to the selected primary identity value.",
316
+ help: "Choose a GTM variable for the main identity value.",
355
317
  enablingConditions: [...IDENTIFY_ADVANCED_MODE_CONDITION],
356
318
  },
357
319
  {
358
320
  type: "SIMPLE_TABLE",
359
321
  name: "identifyTraits",
360
322
  displayName: "Traits",
361
- help: "Add any traits you want to send from GTM variables. Common examples are `email`, `phone`, `name`, and `plan`. Empty rows are ignored.",
323
+ help: "Add any extra fields you want to send from GTM variables. Common examples are `email`, `phone`, `firstName`, `lastName`, and `plan`. Empty rows are ignored.",
362
324
  newRowButtonText: "Add Trait",
363
325
  simpleTableColumns: [
364
326
  {
@@ -380,8 +342,12 @@ export function buildGtmTemplateParameters(_options) {
380
342
  }
381
343
  export function buildGtmRuntimeSource(input) {
382
344
  const loaderScriptBaseUrl = trimTrailingSlash(input?.loaderScriptBaseUrl ?? DEFAULT_GTM_LOADER_SCRIPT_BASE_URL);
383
- const automaticDefaultScopeMappings = buildAutomaticDefaultScopeMappings();
384
- const defaultTemplateScopeMappings = buildHostConfigurableDefaultScopeMappings();
345
+ const runtimeEndpoints = input?.runtimeEndpoints === undefined
346
+ ? undefined
347
+ : {
348
+ apiUrl: input.runtimeEndpoints.apiUrl.trim(),
349
+ coreUrl: input.runtimeEndpoints.coreUrl.trim(),
350
+ };
385
351
  return `
386
352
  const log = require("logToConsole");
387
353
  const injectScript = require("injectScript");
@@ -393,14 +359,17 @@ const copyFromDataLayer = require("copyFromDataLayer");
393
359
 
394
360
  const loaderScriptBaseUrl = ${JSON.stringify(loaderScriptBaseUrl)};
395
361
  const gtmConsentTypes = ${JSON.stringify(GTM_CONSENT_TYPES)};
396
- const automaticDefaultScopeMappings = ${JSON.stringify(automaticDefaultScopeMappings)};
397
- const defaultScopeMappings = ${JSON.stringify(defaultTemplateScopeMappings)};
398
- const hostConfigurableScopes = ${JSON.stringify(defaultTemplateScopeMappings.map((row) => row.scope))};
399
362
  const defaultAutoDetectColorSchemeAttrs = ${JSON.stringify(DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS)};
400
363
  const queueKey = ${JSON.stringify(GTM_QUEUE_KEY)};
401
364
  const intelligentIdentifyUserIdKeys = ${JSON.stringify(INTELLIGENT_IDENTIFY_USER_ID_KEYS)};
402
365
  const intelligentIdentifyEmailKeys = ${JSON.stringify(INTELLIGENT_IDENTIFY_EMAIL_KEYS)};
366
+ const intelligentIdentifyFirstNameKeys = ${JSON.stringify(INTELLIGENT_IDENTIFY_FIRST_NAME_KEYS)};
367
+ const intelligentIdentifyLastNameKeys = ${JSON.stringify(INTELLIGENT_IDENTIFY_LAST_NAME_KEYS)};
403
368
  const intelligentIdentifyPhoneKeys = ${JSON.stringify(INTELLIGENT_IDENTIFY_PHONE_KEYS)};
369
+ const generatedRuntimeEndpoints = ${JSON.stringify(runtimeEndpoints ?? null)};
370
+ const queueClientMeta = ${JSON.stringify(GTM_QUEUE_CLIENT_META)};
371
+ ${RESOLVE_GTM_THEME_INPUT_FROM_TEMPLATE_DATA_SOURCE}
372
+ ${BUILD_GTM_IDENTIFY_COMMAND_SOURCE}
404
373
 
405
374
  const trimString = function(value) {
406
375
  return typeof value === "string" ? value.trim() : "";
@@ -485,12 +454,7 @@ const normalizeTraitValue = function(value) {
485
454
  };
486
455
 
487
456
  const normalizeUserIdValue = function(value) {
488
- if (typeof value !== "string") {
489
- return undefined;
490
- }
491
-
492
- const trimmedValue = value.trim();
493
- return trimmedValue ? trimmedValue : undefined;
457
+ return typeof value === "string" ? trimString(value) || undefined : undefined;
494
458
  };
495
459
 
496
460
  const addTrait = function(result, name, value) {
@@ -520,10 +484,12 @@ const getIntelligentIdentifyValue = function(keys) {
520
484
  };
521
485
 
522
486
  const getIntelligentIdentifyTraits = function() {
523
- const traits = {};
524
- addTrait(traits, "email", getIntelligentIdentifyValue(intelligentIdentifyEmailKeys));
525
- addTrait(traits, "phone", getIntelligentIdentifyValue(intelligentIdentifyPhoneKeys));
526
- return traits;
487
+ return {
488
+ email: getIntelligentIdentifyValue(intelligentIdentifyEmailKeys),
489
+ firstName: getIntelligentIdentifyValue(intelligentIdentifyFirstNameKeys),
490
+ lastName: getIntelligentIdentifyValue(intelligentIdentifyLastNameKeys),
491
+ phone: getIntelligentIdentifyValue(intelligentIdentifyPhoneKeys),
492
+ };
527
493
  };
528
494
 
529
495
  const getAdvancedIdentifyTraits = function(templateData) {
@@ -539,96 +505,27 @@ const getAdvancedIdentifyTraits = function(templateData) {
539
505
  return traits;
540
506
  };
541
507
 
542
- const hasOwnKeys = function(value) {
543
- for (const key in value) {
544
- return !!key;
545
- }
546
- return false;
547
- };
548
-
549
- const getIdentifyCommandFromUserIdAndTraits = function(userId, traits) {
550
- const hasTraits = hasOwnKeys(traits);
551
-
552
- if (userId && hasTraits) {
553
- return {
554
- kind: "identify",
555
- userId: userId,
556
- traits: traits,
557
- };
558
- }
559
- if (userId) {
560
- return {
561
- kind: "identify",
562
- userId: userId,
563
- };
564
- }
565
- if (hasTraits) {
566
- return {
567
- kind: "identify",
568
- traits: traits,
569
- };
570
- }
571
- return undefined;
572
- };
573
-
574
- const getIntelligentIdentifyCommand = function() {
575
- const userId = normalizeUserIdValue(
576
- getIntelligentIdentifyValue(intelligentIdentifyUserIdKeys),
577
- );
578
- const traits = getIntelligentIdentifyTraits();
579
- return getIdentifyCommandFromUserIdAndTraits(userId, traits);
580
- };
581
-
582
- const getAdvancedIdentifyCommand = function(templateData) {
583
- const traits = getAdvancedIdentifyTraits(templateData);
584
- const primaryIdentityType = trimString(templateData.identifyPrimaryIdentityType);
585
- const primaryIdentityValue = normalizeTraitValue(
586
- templateData.identifyPrimaryIdentityValue,
587
- );
588
-
589
- if (primaryIdentityType === "userId") {
590
- const userId = normalizeUserIdValue(primaryIdentityValue);
591
- return getIdentifyCommandFromUserIdAndTraits(userId, traits);
592
- }
593
-
594
- if (
595
- primaryIdentityType === "email" ||
596
- primaryIdentityType === "phone"
597
- ) {
598
- addTrait(traits, primaryIdentityType, primaryIdentityValue);
599
- }
600
-
601
- return hasOwnKeys(traits)
602
- ? {
603
- kind: "identify",
604
- traits: traits,
605
- }
606
- : undefined;
607
- };
608
-
609
508
  const getIdentifyCommand = function(templateData) {
610
509
  if (templateData.identifyMode === "intelligent") {
611
- return getIntelligentIdentifyCommand();
510
+ const intelligentTraits = getIntelligentIdentifyTraits();
511
+ return buildGtmIdentifyCommand({
512
+ email: intelligentTraits.email,
513
+ firstName: intelligentTraits.firstName,
514
+ lastName: intelligentTraits.lastName,
515
+ mode: "intelligent",
516
+ phone: intelligentTraits.phone,
517
+ userId: getIntelligentIdentifyValue(intelligentIdentifyUserIdKeys),
518
+ });
612
519
  }
613
520
  if (templateData.identifyMode === "advanced") {
614
- return getAdvancedIdentifyCommand(templateData);
615
- }
616
- return undefined;
617
- };
618
-
619
- const getThemeInput = function(templateData) {
620
- if (templateData.themeMode === "variable") {
621
- return templateData.themeModeVariable;
622
- }
623
- if (templateData.themeMode === "host") {
624
- return templateData.themeAttr;
625
- }
626
- if (
627
- templateData.themeMode === "light" ||
628
- templateData.themeMode === "dark" ||
629
- templateData.themeMode === "system"
630
- ) {
631
- return templateData.themeMode;
521
+ return buildGtmIdentifyCommand({
522
+ mode: "advanced",
523
+ primaryIdentityType: trimString(templateData.identifyPrimaryIdentityType),
524
+ primaryIdentityValue: normalizeTraitValue(
525
+ templateData.identifyPrimaryIdentityValue,
526
+ ),
527
+ traits: getAdvancedIdentifyTraits(templateData),
528
+ });
632
529
  }
633
530
  return undefined;
634
531
  };
@@ -644,144 +541,78 @@ const getConsentState = function() {
644
541
  return result;
645
542
  };
646
543
 
647
- const dedupeScopes = function(scopes) {
648
- const seen = {};
649
- const result = [];
650
- for (let index = 0; index < scopes.length; index += 1) {
651
- const scope = scopes[index];
652
- if (typeof scope !== "string" || seen[scope]) {
653
- continue;
544
+ const buildDefaultConsent = function(templateData) {
545
+ const consentState = getConsentState();
546
+ const grantedScopeLookup = {};
547
+ const grantedScopes = [];
548
+ const grantScope = function(scope) {
549
+ if (!scope || grantedScopeLookup[scope]) {
550
+ return;
654
551
  }
655
- seen[scope] = true;
656
- result.push(scope);
657
- }
658
- return result;
659
- };
552
+ grantedScopeLookup[scope] = true;
553
+ grantedScopes.push(scope);
554
+ };
660
555
 
661
- const isKnownConsentType = function(consentType) {
662
- for (let index = 0; index < gtmConsentTypes.length; index += 1) {
663
- if (gtmConsentTypes[index] === consentType) {
664
- return true;
665
- }
556
+ if (consentState.analytics_storage === "granted") {
557
+ grantScope("analytics.storage");
558
+ grantScope("analytics.measurement");
666
559
  }
667
- return false;
668
- };
669
-
670
- const isHostConfigurableScope = function(scope) {
671
- for (let index = 0; index < hostConfigurableScopes.length; index += 1) {
672
- if (hostConfigurableScopes[index] === scope) {
673
- return true;
674
- }
560
+ if (consentState.personalization_storage === "granted") {
561
+ grantScope("personalization.storage");
675
562
  }
676
- return false;
677
- };
678
-
679
- const getManualScopeMappings = function(templateData) {
680
- const rawRows = getSimpleTableRows(templateData.scopeMappings);
681
- const rows = rawRows.length > 0 ? rawRows : defaultScopeMappings;
682
- const mappings = [];
683
- for (let index = 0; index < rows.length; index += 1) {
684
- const row = rows[index];
685
- if (!row || typeof row !== "object") {
686
- continue;
687
- }
688
- const scope = trimString(row.scope);
689
- const consentType = trimString(row.mapping);
690
- if (!scope || !consentType) {
691
- continue;
692
- }
693
- if (!isHostConfigurableScope(scope) || !isKnownConsentType(consentType)) {
694
- continue;
695
- }
696
- mappings.push({
697
- scope: scope,
698
- consentType: consentType,
699
- });
563
+ if (consentState.ad_storage === "granted") {
564
+ grantScope("ads.storage");
700
565
  }
701
- return mappings;
702
- };
703
-
704
- const getAnalyticsMeasurementMode = function(templateData) {
705
- if (
706
- templateData.analyticsMeasurementMode === "granted" ||
707
- templateData.analyticsMeasurementMode === "denied"
708
- ) {
709
- return templateData.analyticsMeasurementMode;
566
+ if (consentState.ad_user_data === "granted") {
567
+ grantScope("ads.user_data");
710
568
  }
711
- return "inherit-analytics-storage";
712
- };
713
-
714
- const buildDefaultConsent = function(templateData) {
715
- const consentState = getConsentState();
716
- const grantedScopes = [];
717
-
718
- if (templateData.consentMode === "manual") {
719
- const scopeMappings = getManualScopeMappings(templateData);
720
- for (let index = 0; index < scopeMappings.length; index += 1) {
721
- const mapping = scopeMappings[index];
722
- if (consentState[mapping.consentType] === "granted") {
723
- grantedScopes.push(mapping.scope);
724
- }
725
- }
726
- } else {
727
- for (let index = 0; index < automaticDefaultScopeMappings.length; index += 1) {
728
- const mapping = automaticDefaultScopeMappings[index];
729
- if (consentState[mapping.mapping] === "granted") {
730
- grantedScopes.push(mapping.scope);
731
- }
732
- }
733
-
734
- const analyticsMeasurementMode = getAnalyticsMeasurementMode(templateData);
735
- if (analyticsMeasurementMode === "granted") {
736
- grantedScopes.push("analytics.measurement");
737
- }
738
- if (
739
- analyticsMeasurementMode === "inherit-analytics-storage" &&
740
- consentState.analytics_storage === "granted"
741
- ) {
742
- grantedScopes.push("analytics.measurement");
743
- }
569
+ if (consentState.ad_personalization === "granted") {
570
+ grantScope("ads.personalization");
744
571
  }
745
572
 
746
- const dedupedScopes = dedupeScopes(grantedScopes);
747
- return dedupedScopes.length > 0 ? dedupedScopes : undefined;
573
+ return grantedScopes.length > 0 ? grantedScopes : undefined;
748
574
  };
749
575
 
750
- const onSuccess = function() {
751
- if (typeof data.gtmOnSuccess === "function") {
752
- data.gtmOnSuccess();
753
- }
576
+ let requestSequence = 0;
577
+
578
+ const enqueuePublicCommand = function(queuePush, instanceId, command) {
579
+ requestSequence += 1;
580
+ const requestId = "gtm_" + command.kind + "_" + requestSequence;
581
+ queuePush({
582
+ version: "1",
583
+ instanceId: instanceId,
584
+ requestId: requestId,
585
+ idempotencyKey: requestId,
586
+ clientMeta: queueClientMeta,
587
+ command: command,
588
+ });
754
589
  };
755
590
 
756
- const onFailure = function() {
591
+ const apiKey = trimString(data.apiKey);
592
+ if (!apiKey) {
757
593
  if (typeof data.gtmOnFailure === "function") {
758
594
  data.gtmOnFailure();
759
595
  }
760
- };
761
-
762
- const apiKey = trimString(data.apiKey);
763
- if (!apiKey) {
764
- onFailure();
765
596
  return;
766
597
  }
767
598
 
768
599
  const encodedApiKey = encodeUriComponent(apiKey);
769
600
  if (!encodedApiKey) {
770
- onFailure();
601
+ if (typeof data.gtmOnFailure === "function") {
602
+ data.gtmOnFailure();
603
+ }
771
604
  return;
772
605
  }
773
-
774
606
  const widgetUrl = loaderScriptBaseUrl + "/" + encodedApiKey + "/loader.js";
775
607
  const queuePush = createQueue(queueKey);
608
+ const instanceId = "gtm-" + encodedApiKey;
776
609
  const initOptions = {
777
610
  apiKey: apiKey,
778
- clientMeta: {
779
- loader: "gtm",
780
- transport: "tag-manager",
781
- },
611
+ clientMeta: queueClientMeta,
782
612
  };
783
613
 
784
- const colorScheme = normalizeThemeInput(getThemeInput(data));
614
+ const themeInput = resolveGtmThemeInputFromTemplateData(data);
615
+ const colorScheme = normalizeThemeInput(themeInput);
785
616
  if (colorScheme !== undefined) {
786
617
  initOptions.colorScheme = colorScheme;
787
618
  }
@@ -791,33 +622,50 @@ if (defaultConsent !== undefined) {
791
622
  initOptions.defaultConsent = defaultConsent;
792
623
  }
793
624
 
794
- queuePush({
625
+ if (generatedRuntimeEndpoints !== null) {
626
+ initOptions.runtimeEndpoints = {
627
+ apiUrl: generatedRuntimeEndpoints.apiUrl,
628
+ coreUrl: generatedRuntimeEndpoints.coreUrl,
629
+ };
630
+ }
631
+
632
+ enqueuePublicCommand(queuePush, instanceId, {
795
633
  kind: "init",
796
634
  opts: initOptions,
797
635
  });
798
636
 
799
637
  const identifyCommand = getIdentifyCommand(data);
800
638
  if (identifyCommand !== undefined) {
801
- queuePush(identifyCommand);
639
+ enqueuePublicCommand(queuePush, instanceId, identifyCommand);
802
640
  }
803
641
 
804
642
  log("Loading getuserfeedback loader:", widgetUrl);
805
643
 
806
644
  if (queryPermission("inject_script", widgetUrl)) {
807
- injectScript(widgetUrl, onSuccess, onFailure, "getuserfeedback");
645
+ injectScript(
646
+ widgetUrl,
647
+ typeof data.gtmOnSuccess === "function" ? data.gtmOnSuccess : undefined,
648
+ typeof data.gtmOnFailure === "function" ? data.gtmOnFailure : undefined,
649
+ "getuserfeedback",
650
+ );
808
651
  } else {
809
- onFailure();
652
+ if (typeof data.gtmOnFailure === "function") {
653
+ data.gtmOnFailure();
654
+ }
810
655
  }
811
656
  `.trim();
812
657
  }
813
658
  export function buildGtmPermissions(input) {
814
659
  const loaderScriptBaseUrl = trimTrailingSlash(input?.loaderScriptBaseUrl ?? DEFAULT_GTM_LOADER_SCRIPT_BASE_URL);
815
- const loaderUrlPattern = `${loaderScriptBaseUrl}/*/loader.js`;
660
+ const loaderUrlPatterns = [
661
+ `${loaderScriptBaseUrl}/*/loader.js`,
662
+ ...normalizeAdditionalLoaderScriptUrlPatterns(input?.additionalLoaderScriptUrlPatterns),
663
+ ];
816
664
  return [
817
665
  buildPermission("inject_script", [
818
666
  {
819
667
  key: "urls",
820
- value: listValue([stringValue(loaderUrlPattern)]),
668
+ value: listValue(loaderUrlPatterns.map((pattern) => stringValue(pattern))),
821
669
  },
822
670
  ]),
823
671
  buildPermission("logging", [
@@ -855,6 +703,8 @@ export function buildGtmPermissions(input) {
855
703
  value: listValue([
856
704
  ...INTELLIGENT_IDENTIFY_USER_ID_KEYS,
857
705
  ...INTELLIGENT_IDENTIFY_EMAIL_KEYS,
706
+ ...INTELLIGENT_IDENTIFY_FIRST_NAME_KEYS,
707
+ ...INTELLIGENT_IDENTIFY_LAST_NAME_KEYS,
858
708
  ...INTELLIGENT_IDENTIFY_PHONE_KEYS,
859
709
  ].map((key) => stringValue(key))),
860
710
  },
@@ -882,3 +732,32 @@ export function buildGtmTemplateParts(input) {
882
732
  export function buildGtmTemplateArtifact(input) {
883
733
  return buildGtmTemplateParts(input);
884
734
  }
735
+ export function buildGtmTemplateFileSource(input) {
736
+ const artifact = buildGtmTemplateArtifact(input);
737
+ const infoOverrides = input?.infoOverrides;
738
+ const info = {
739
+ type: artifact.info.type,
740
+ id: normalizeTemplateStringSection(infoOverrides?.id, GTM_TEMPLATE_DEFAULT_ID),
741
+ version: artifact.info.version,
742
+ securityGroups: normalizeTemplateStringArray(infoOverrides?.securityGroups, []),
743
+ displayName: normalizeTemplateStringSection(infoOverrides?.displayName, GTM_TEMPLATE_GALLERY_DISPLAY_NAME),
744
+ categories: normalizeTemplateStringArray(infoOverrides?.categories, GTM_TEMPLATE_GALLERY_CATEGORIES),
745
+ brand: {
746
+ displayName: normalizeTemplateStringSection(infoOverrides?.brandDisplayName, GTM_TEMPLATE_DEFAULT_BRAND_DISPLAY_NAME),
747
+ thumbnail: normalizeTemplateStringSection(infoOverrides?.brandThumbnail, GTM_TEMPLATE_DEFAULT_BRAND_THUMBNAIL),
748
+ },
749
+ description: normalizeTemplateStringSection(infoOverrides?.description, GTM_TEMPLATE_GALLERY_DESCRIPTION),
750
+ containerContexts: [...artifact.info.containerContexts],
751
+ };
752
+ const sections = [
753
+ renderTemplateSection("___TERMS_OF_SERVICE___", normalizeTemplateStringSection(input?.termsOfService, GTM_TEMPLATE_GALLERY_TERMS_OF_SERVICE)),
754
+ renderTemplateSection("___INFO___", JSON.stringify(info, null, 2)),
755
+ renderTemplateSection("___TEMPLATE_PARAMETERS___", JSON.stringify(artifact.parameters, null, 2)),
756
+ renderTemplateSection("___SANDBOXED_JS_FOR_WEB_TEMPLATE___", artifact.runtimeSource),
757
+ renderTemplateSection("___WEB_PERMISSIONS___", JSON.stringify(artifact.permissions, null, 2)),
758
+ renderTemplateSection("___TESTS___", normalizeTemplateStringSection(input?.tests, GTM_TEMPLATE_DEFAULT_TESTS)),
759
+ renderTemplateSection("___NOTES___", normalizeTemplateStringSection(input?.notes, GTM_TEMPLATE_DEFAULT_NOTES)),
760
+ "",
761
+ ];
762
+ return `${input?.includeUtf8Bom === false ? "" : GTM_TEMPLATE_UTF8_BOM}${sections.join("\n\n")}`;
763
+ }