@capillarytech/creatives-library 8.0.353-alpha.6 → 8.0.354

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.
Files changed (127) hide show
  1. package/constants/unified.js +0 -29
  2. package/index.html +1 -0
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +20 -35
  5. package/utils/cdnTransformation.js +63 -3
  6. package/utils/commonUtils.js +1 -19
  7. package/utils/tests/cdnTransformation.test.js +111 -0
  8. package/v2Components/CapActionButton/constants.js +0 -7
  9. package/v2Components/CapActionButton/index.js +108 -166
  10. package/v2Components/CapActionButton/index.scss +6 -157
  11. package/v2Components/CapActionButton/messages.js +3 -19
  12. package/v2Components/CapActionButton/tests/index.test.js +17 -41
  13. package/v2Components/CapTagList/index.js +0 -10
  14. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -72
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -213
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  21. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  22. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -157
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -346
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  25. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  26. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  27. package/v2Components/CommonTestAndPreview/index.js +186 -691
  28. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  29. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  30. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  31. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  32. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  33. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  34. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  35. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  36. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  37. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  38. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  39. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +26 -36
  40. package/v2Components/FormBuilder/index.js +6 -11
  41. package/v2Components/TemplatePreview/_templatePreview.scss +23 -38
  42. package/v2Components/TemplatePreview/index.js +31 -143
  43. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  44. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  45. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  46. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  47. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  48. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  49. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  50. package/v2Containers/CreativesContainer/constants.js +0 -9
  51. package/v2Containers/CreativesContainer/index.js +103 -322
  52. package/v2Containers/CreativesContainer/index.scss +1 -51
  53. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  54. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  55. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  56. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  57. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  58. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  59. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  60. package/v2Containers/Rcs/constants.js +10 -119
  61. package/v2Containers/Rcs/index.js +818 -2450
  62. package/v2Containers/Rcs/index.scss +8 -280
  63. package/v2Containers/Rcs/messages.js +3 -34
  64. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
  65. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  66. package/v2Containers/Rcs/tests/index.test.js +121 -152
  67. package/v2Containers/Rcs/tests/mockData.js +0 -38
  68. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  69. package/v2Containers/Rcs/utils.js +11 -478
  70. package/v2Containers/Sms/Create/index.js +40 -106
  71. package/v2Containers/SmsTrai/Create/index.js +4 -9
  72. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  73. package/v2Containers/SmsTrai/Edit/index.js +130 -640
  74. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  75. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  76. package/v2Containers/SmsWrapper/index.js +8 -37
  77. package/v2Containers/TagList/index.js +0 -6
  78. package/v2Containers/Templates/_templates.scss +9 -166
  79. package/v2Containers/Templates/actions.js +0 -11
  80. package/v2Containers/Templates/constants.js +0 -2
  81. package/v2Containers/Templates/index.js +52 -120
  82. package/v2Containers/Templates/sagas.js +18 -57
  83. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1017 -1062
  84. package/v2Containers/Templates/tests/sagas.test.js +39 -205
  85. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  86. package/v2Containers/TemplatesV2/index.js +23 -86
  87. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  88. package/v2Containers/Whatsapp/index.js +20 -3
  89. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  90. package/utils/rcsPayloadUtils.js +0 -92
  91. package/utils/templateVarUtils.js +0 -201
  92. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  93. package/utils/tests/templateVarUtils.test.js +0 -204
  94. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  95. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  96. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  97. package/v2Components/SmsFallback/constants.js +0 -73
  98. package/v2Components/SmsFallback/index.js +0 -956
  99. package/v2Components/SmsFallback/index.scss +0 -265
  100. package/v2Components/SmsFallback/messages.js +0 -78
  101. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -119
  102. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  103. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  104. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  105. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -223
  106. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  107. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  108. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  109. package/v2Components/TemplatePreview/constants.js +0 -2
  110. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  111. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  112. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  113. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  114. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  115. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  116. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  117. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  118. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  119. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  120. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  121. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  122. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  123. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  124. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  125. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  126. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  127. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -160,24 +160,6 @@ export const TAG_CONTENT_REGEX = /{{([^}]+)}}/g;
160
160
  export const ENTRY_TRIGGER_TAG_REGEX = /\bentryTrigger\.\w+(?:\.\w+)?(?:\(\w+\))?/g;
161
161
  export const SKIP_TAGS_REGEX_GROUPS = ["dynamic_expiry_date_after_\\d+_days.FORMAT_\\d", "unsubscribe\\(#[a-zA-Z\\d]{6}\\)", "Link_to_[a-zA-Z]", "SURVEY.*.TOKEN", "^[A-Za-z].*\\([a-zA-Z\\d]*\\)", "referral_unique_(code|url).*userid"];
162
162
 
163
- // --- Template variable tokens (`{{var}}`, DLT `{#var#}`) ---
164
- /** Global: all `{{…}}` placeholders in a string. */
165
- export const DEFAULT_MUSTACHE_VAR_REGEX = /\{\{[^}]+\}\}/g;
166
- /** Global: `{{…}}` or DLT `{#…#}` tokens (SMS combined mode). */
167
- export const COMBINED_SMS_TEMPLATE_VAR_REGEX = /\{\{[^}]+\}\}|\{\#[^#]*\#\}/g;
168
- /** Full-string check: one mustache token. */
169
- export const MUSTACHE_VAR_TOKEN_FULL_STRING_REGEX = /^\{\{[^}]+\}\}$/;
170
- /** Full-string check: one DLT hash token. */
171
- export const DLT_HASH_VAR_TOKEN_FULL_STRING_REGEX = /^\{\#[^#]*\#\}$/;
172
- /** Full-string with capture group: inner name for `{{ name }}`-style tokens (whitespace-tolerant). */
173
- export const MUSTACHE_TOKEN_INNER_NAME_REGEX = /^\{\{\s*([^}]+?)\s*\}\}$/;
174
- /** Full-string with capture group: inner name/body for `{# name #}` DLT tokens (whitespace-tolerant). */
175
- export const DLT_HASH_TOKEN_INNER_NAME_REGEX = /^\{#\s*(.*?)\s*#\}$/;
176
- /** Global with capture group: inner name inside `{{name}}`. */
177
- export const MUSTACHE_VAR_NAME_CAPTURE_REGEX = /\{\{([^}]+)\}\}/g;
178
- /** Global with capture group: inner body inside `{#body#}`. */
179
- export const DLT_VAR_BODY_CAPTURE_REGEX = /\{\#([^#]*)\#\}/g;
180
-
181
163
  export const GET_TRANSLATION_MAPPED = {
182
164
  'en': 'en-US',
183
165
  'zh-cn': 'zh',
@@ -215,14 +197,3 @@ export const LOGOUT_FAILURE = 'cap/LOGOUT_FAILURE';
215
197
  export const JAPANESE_HELP_TEXT = 'ヘルプ :トークンの定義';
216
198
 
217
199
  export const TAG_TRANSLATION_DOC = 'https://docs.capillarytech.com/docs/tags-translation';
218
-
219
- // --- RCS SMS fallback API contract (shared across modules: campaigns, journey, etc.) ---
220
-
221
- /** Keys on `messageContent.*.smsFallBackContent` sent to the API: only `message` + `templateConfigs`.
222
- * `rcsSmsFallbackVarMapped` is editor-only — merged into `message` at normalize, not sent on the wire. */
223
- export const RCS_API_SMS_FALLBACK_KEYS = Object.freeze({
224
- MESSAGE: 'message',
225
- TEMPLATE_CONFIGS: 'templateConfigs',
226
- RCS_SMS_FALLBACK_VAR_MAPPED: 'rcsSmsFallbackVarMapped',
227
- });
228
-
package/index.html CHANGED
@@ -23,6 +23,7 @@
23
23
  <!-- End Google Tag Manager -->
24
24
  <script>try{Typekit.load({ async: true });}catch(e){console.log(e)}</script>
25
25
  <title>Capillary - Creatives</title>
26
+ <script>try{window.APP_ENV=__ENV_OBJECT__;}catch(e){window.APP_ENV={};}</script>
26
27
  </head>
27
28
  <body>
28
29
  <noscript>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.353-alpha.6",
4
+ "version": "8.0.354",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -89,26 +89,21 @@ describe('uploadFile -- whatsapp image upload', () => {
89
89
 
90
90
  it('Uploads the file with the original filename when encodeURIComponent fails', async () => {
91
91
  // Mocking the encodeURIComponent function to throw an error
92
- const originalEncodeURIComponent = global.encodeURIComponent;
93
92
  global.encodeURIComponent = jest.fn(() => { throw new Error('encodeURIComponent error'); });
94
93
  const blob = new Blob([''], { type: 'image/jpeg' });
95
94
  const file = new File([blob], '@%test.jpeg', { type: 'image/jpeg' });
96
- try {
97
- expect(
98
- uploadFile({
99
- file,
100
- assetType: 'image',
101
- fileParams: {
102
- width: 275,
103
- height: 183,
104
- error: false,
105
- },
106
- whatsappParams: {},
107
- }),
108
- ).toEqual(Promise.resolve());
109
- } finally {
110
- global.encodeURIComponent = originalEncodeURIComponent;
111
- }
95
+ expect(
96
+ uploadFile({
97
+ file,
98
+ assetType: 'image',
99
+ fileParams: {
100
+ width: 275,
101
+ height: 183,
102
+ error: false,
103
+ },
104
+ whatsappParams: {},
105
+ }),
106
+ ).toEqual(Promise.resolve());
112
107
  });
113
108
  });
114
109
 
@@ -1042,26 +1037,16 @@ describe('getMembersLookup', () => {
1042
1037
  expect(result).toBeInstanceOf(Promise);
1043
1038
  });
1044
1039
 
1045
- it('should call fetch with correct URL encoding and GET method', async () => {
1040
+ it('should call fetch with correct URL encoding and GET method', () => {
1046
1041
  global.fetch.mockClear();
1047
- await getMembersLookup('email', 'user+test@example.com');
1042
+ getMembersLookup('email', 'user+test@example.com');
1048
1043
  expect(global.fetch).toHaveBeenCalled();
1049
-
1050
- // Find the first call that uses both the members endpoint and proper encoding
1051
- const call = global.fetch.mock.calls.find(
1052
- ([url]) =>
1053
- url &&
1054
- url.includes('members') &&
1055
- url.includes('identifierType=email') &&
1056
- url.includes('identifierValue=user%2Btest%40example.com')
1057
- );
1058
- expect(call).toBeDefined();
1059
-
1060
- // Check URL structure
1061
- const [url, options] = call;
1062
- expect(url).toContain('identifierType=email');
1063
- expect(url).toContain('identifierValue=user%2Btest%40example.com');
1064
- expect((options && options.method) || 'GET').toBe('GET');
1044
+ const calls = global.fetch.mock.calls;
1045
+ const anyMembersCall = calls.find((c) => c[0] && String(c[0]).includes('members'));
1046
+ expect(anyMembersCall).toBeDefined();
1047
+ expect(anyMembersCall[0]).toContain('identifierType=');
1048
+ expect(anyMembersCall[0]).toContain('identifierValue=');
1049
+ expect(anyMembersCall[1]?.method || 'GET').toBe('GET');
1065
1050
  });
1066
1051
  });
1067
1052
 
@@ -422,14 +422,74 @@ export function removeAllCdnLocalStorageItems() {
422
422
  }
423
423
 
424
424
  /**
425
- *
426
- * @param {*} configsResponse
425
+ * Safe JSON.parse — returns fallback if value is empty or malformed.
426
+ * Used to parse the JSON-string env vars (qualityCfg, mapping, s3CdnMap) injected via window.APP_ENV.
427
+ */
428
+ function safeJsonParse(value, fallback) {
429
+ if (!value) return fallback;
430
+ try {
431
+ return JSON.parse(value);
432
+ } catch (e) {
433
+ return fallback;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Reads CDN config from window.APP_ENV (injected at container startup by entrypoint.sh)
439
+ * and populates localStorage via saveCdnConfigs — same shape as the legacy API response.
440
+ *
441
+ * Returns true if env config was found and applied, false otherwise (saga falls back to API).
442
+ *
443
+ * In deployed containers, window.APP_ENV is populated → no API call. In local dev
444
+ * (`npm start`), window.APP_ENV is empty → returns false → saga falls back to the API
445
+ * (legacy path, retained for the rollout window).
446
+ *
447
+ * Closes the VAPT info-disclosure surface (CAP-183204) by eliminating the
448
+ * /getCdnTransformationConfig API response in production.
449
+ */
450
+ export function initCdnConfigFromEnv() {
451
+ try {
452
+ const env = (typeof window !== 'undefined' && window.APP_ENV) || {};
453
+
454
+ // All four fields are required to construct a CDN URL. If any is missing,
455
+ // partial config would silently make every getCdnUrl call fall through to the
456
+ // raw S3 URL. Return false so the saga falls back to the API (which may still
457
+ // have the values during the cluster-by-cluster rollout window).
458
+ if (!env.CDN_HOSTNAME
459
+ || !env.CDN_IMG_TRANSFORMATION_URL_SUFFIX
460
+ || !env.CREATIVE_ASSETS_BUCKET_PATH
461
+ || !env.S3_CDN_MAP) {
462
+ return false;
463
+ }
464
+
465
+ saveCdnConfigs({
466
+ hostname: env.CDN_HOSTNAME,
467
+ transformationUrlSuffix: env.CDN_IMG_TRANSFORMATION_URL_SUFFIX,
468
+ bucketPath: env.CREATIVE_ASSETS_BUCKET_PATH,
469
+ qualityCfg: safeJsonParse(env.CDN_IMG_QUALITY_CFG, {}),
470
+ overrideEmailQuality: env.OVERRIDE_IMAGE_QUALITY === 'true',
471
+ overrideEmailQualityMapping: safeJsonParse(env.IMAGE_QUALITY_MAPPING, []),
472
+ s3CdnMap: safeJsonParse(env.S3_CDN_MAP, {}),
473
+ });
474
+ return true;
475
+ } catch (e) {
476
+ Bugsnag.leaveBreadcrumb('initCdnConfigFromEnv failed to apply window.APP_ENV');
477
+ Bugsnag.notify(e, (event) => {
478
+ event.severity = 'error';
479
+ });
480
+ return false;
481
+ }
482
+ }
483
+
484
+ /**
485
+ *
486
+ * @param {*} configsResponse
427
487
  * This util function saves the getCdnConfigs response into the local storage.
428
488
  * The following response items are mapped into the following keys in local storage:
429
489
  * hostname -> CREATIVES_CDN_BASE_URL
430
490
  * qualityCfg ->CREATIVES_CDN_QUALITY_CONFIG
431
491
  * transformationUrlSuffix -> CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX
432
- *
492
+ *
433
493
  * 1. If configsReponse is empty. All above mentioned keys are deleted from localstorage.
434
494
  * 2. If any one of the above keys is missing. The respective keys are deleted from localstorage.
435
495
  * 3. Else it is saved into the localstorage.
@@ -539,22 +539,4 @@ export const isValidMobile = (mobile) => PHONE_REGEX.test(mobile);
539
539
  export const formatPhoneNumber = (phone) => {
540
540
  if (!phone) return '';
541
541
  return String(phone).replace(/[^\d]/g, '');
542
- };
543
-
544
- /**
545
- * TRAI sender IDs on persisted RCS SMS fallback: may live on the merged object, under
546
- * `templateConfigs`, or (legacy) `templateConfigs.header`. Same resolution order as
547
- * `createPayload` in `Rcs/index.js`.
548
- */
549
- export function extractRegisteredSenderIdsFromSmsFallbackRecord(record) {
550
- if (!record || typeof record !== 'object') return null;
551
- const tc = record.templateConfigs && typeof record.templateConfigs === 'object'
552
- ? record.templateConfigs
553
- : {};
554
- const candidates = [record.registeredSenderIds, tc.registeredSenderIds, tc.header];
555
- for (let i = 0; i < candidates.length; i += 1) {
556
- const a = candidates[i];
557
- if (Array.isArray(a) && a.length > 0) return a;
558
- }
559
- return null;
560
- }
542
+ };
@@ -546,6 +546,117 @@ describe("cdnTransformationTests", () => {
546
546
  });
547
547
  });
548
548
 
549
+ describe("initCdnConfigFromEnv()", () => {
550
+ afterEach(() => {
551
+ delete window.APP_ENV;
552
+ });
553
+
554
+ it("returns false when window.APP_ENV is undefined", () => {
555
+ delete window.APP_ENV;
556
+ expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
557
+ });
558
+
559
+ const completeEnv = {
560
+ CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
561
+ CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
562
+ CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
563
+ OVERRIDE_IMAGE_QUALITY: "false",
564
+ CDN_IMG_QUALITY_CFG: "{}",
565
+ IMAGE_QUALITY_MAPPING: "[]",
566
+ S3_CDN_MAP: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
567
+ };
568
+
569
+ it.each([
570
+ ["CDN_HOSTNAME"],
571
+ ["CDN_IMG_TRANSFORMATION_URL_SUFFIX"],
572
+ ["CREATIVE_ASSETS_BUCKET_PATH"],
573
+ ["S3_CDN_MAP"],
574
+ ])("returns false when required field %s is missing", (missingKey) => {
575
+ window.APP_ENV = { ...completeEnv };
576
+ delete window.APP_ENV[missingKey];
577
+ expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
578
+ });
579
+
580
+ it.each([
581
+ ["CDN_HOSTNAME"],
582
+ ["CDN_IMG_TRANSFORMATION_URL_SUFFIX"],
583
+ ["CREATIVE_ASSETS_BUCKET_PATH"],
584
+ ["S3_CDN_MAP"],
585
+ ])("returns false when required field %s is empty string", (emptyKey) => {
586
+ window.APP_ENV = { ...completeEnv, [emptyKey]: "" };
587
+ expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
588
+ });
589
+
590
+ it("populates localStorage and returns true for a complete env", () => {
591
+ window.APP_ENV = {
592
+ CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
593
+ CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
594
+ CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
595
+ OVERRIDE_IMAGE_QUALITY: "true",
596
+ CDN_IMG_QUALITY_CFG: '{"EMAIL":65,"DEFAULT":70}',
597
+ IMAGE_QUALITY_MAPPING: '[[30,90],[80,85]]',
598
+ S3_CDN_MAP: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
599
+ };
600
+
601
+ expect(cdnUtils.initCdnConfigFromEnv()).toBe(true);
602
+ expect(localStorage.getItem("CREATIVES_CDN_BASE_URL")).toBe("https://storage.crm.n.content-cdn.io");
603
+ expect(localStorage.getItem("CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX")).toBe("cdn-cgi/image");
604
+ expect(localStorage.getItem("CREATIVES_S3_BUCKET_PATH")).toBe("intouch_creative_assets");
605
+ expect(localStorage.getItem("CREATIVES_CDN_QUALITY_CONFIG")).toBe('{"EMAIL":65,"DEFAULT":70}');
606
+ expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY")).toBe("true");
607
+ expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING")).toBe("[[30,90],[80,85]]");
608
+ expect(JSON.parse(localStorage.getItem("S3_CDN_MAP"))).toEqual({
609
+ "host.s3.amazonaws.com": { cdn_host: "cdn.example.com", bucket_path: "x" },
610
+ });
611
+ });
612
+
613
+ it("uses fallback values for malformed JSON env vars instead of throwing", () => {
614
+ window.APP_ENV = {
615
+ CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
616
+ CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
617
+ CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
618
+ OVERRIDE_IMAGE_QUALITY: "false",
619
+ CDN_IMG_QUALITY_CFG: "not-valid-json{{",
620
+ IMAGE_QUALITY_MAPPING: "also-broken",
621
+ S3_CDN_MAP: "}{",
622
+ };
623
+
624
+ expect(cdnUtils.initCdnConfigFromEnv()).toBe(true);
625
+ expect(localStorage.getItem("CREATIVES_CDN_BASE_URL")).toBe("https://storage.crm.n.content-cdn.io");
626
+ });
627
+
628
+ it("treats OVERRIDE_IMAGE_QUALITY values other than the string \"true\" as false", () => {
629
+ window.APP_ENV = {
630
+ CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
631
+ CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
632
+ CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
633
+ OVERRIDE_IMAGE_QUALITY: "no",
634
+ CDN_IMG_QUALITY_CFG: "{}",
635
+ IMAGE_QUALITY_MAPPING: "[]",
636
+ S3_CDN_MAP: "{}",
637
+ };
638
+
639
+ cdnUtils.initCdnConfigFromEnv();
640
+ expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY")).toBe(undefined);
641
+ });
642
+
643
+ it("returns false and notifies Bugsnag when window.APP_ENV access throws", () => {
644
+ // Force the `window.APP_ENV` read inside initCdnConfigFromEnv to throw,
645
+ // which exercises the function's outer catch block.
646
+ Object.defineProperty(window, "APP_ENV", {
647
+ get() { throw new Error("APP_ENV access boom"); },
648
+ configurable: true,
649
+ });
650
+ const breadcrumbSpy = jest.spyOn(Bugsnag, "leaveBreadcrumb");
651
+
652
+ expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
653
+ expect(breadcrumbSpy).toHaveBeenCalledWith(
654
+ "initCdnConfigFromEnv failed to apply window.APP_ENV"
655
+ );
656
+ expect(bugsnagSpy).toHaveBeenCalled();
657
+ });
658
+ });
659
+
549
660
  describe("getEmailImageOverrideQuality()",()=>{
550
661
  it("Should return quality of last element in array if file size is greater than max provided in array",()=>{
551
662
  localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
@@ -32,13 +32,6 @@ export const CTA_OPTIONS = [
32
32
  ];
33
33
 
34
34
  export const invalidVarRegex = /{{(.*?)}}/g;
35
-
36
- /** URL CTA subtype (Figma: URL type dropdown). */
37
- export const RCS_CTA_URL_TYPE = {
38
- STATIC: 'STATIC',
39
- DYNAMIC: 'DYNAMIC',
40
- };
41
-
42
35
  export const BTN_MAX_LENGTH = 25;
43
36
  export const PHONE_NUMBER_MAX_LENGTH = 15;
44
37
  export const URL_MAX_LENGTH = 2000;