@capillarytech/creatives-library 8.0.359-alpha.0 → 8.0.359-alpha.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.
- package/constants/unified.js +29 -0
- package/index.html +1 -0
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/cdnTransformation.js +75 -3
- package/utils/commonUtils.js +19 -1
- package/utils/rcsPayloadUtils.js +92 -0
- package/utils/templateVarUtils.js +201 -0
- package/utils/tests/cdnTransformation.test.js +127 -0
- package/utils/tests/rcsPayloadUtils.test.js +226 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +166 -108
- package/v2Components/CapActionButton/index.scss +157 -6
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +41 -17
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +16 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +14 -132
- package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +400 -239
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +202 -10
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +40 -2
- package/v2Components/CommonTestAndPreview/index.js +887 -453
- package/v2Components/CommonTestAndPreview/messages.js +45 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +25 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +163 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +0 -364
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +454 -1
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +327 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
- package/v2Components/FormBuilder/index.js +167 -56
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +956 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +37 -22
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -31
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/App/constants.js +3 -0
- package/v2Containers/App/tests/constants.test.js +61 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
- package/v2Containers/CreativesContainer/constants.js +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
- package/v2Containers/CreativesContainer/index.js +382 -127
- package/v2Containers/CreativesContainer/index.scss +83 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/constants.js +120 -11
- package/v2Containers/Rcs/index.js +2577 -812
- package/v2Containers/Rcs/index.scss +281 -8
- package/v2Containers/Rcs/messages.js +34 -3
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98036 -70145
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/index.test.js +152 -121
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
- package/v2Containers/Rcs/tests/utils.test.js +646 -30
- package/v2Containers/Rcs/utils.js +478 -11
- package/v2Containers/Sms/Create/index.js +106 -40
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +640 -130
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +166 -86
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +203 -145
- package/v2Containers/Templates/sagas.js +62 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
- package/v2Containers/Templates/tests/sagas.test.js +222 -22
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/tests/webpush.test.js +375 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/Viber/constants.js +0 -19
- package/v2Containers/Viber/index.js +47 -714
- package/v2Containers/Viber/index.scss +0 -148
- package/v2Containers/Viber/messages.js +0 -116
- package/v2Containers/Viber/tests/index.test.js +0 -80
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/WebPush/Create/index.js +91 -8
- package/v2Containers/WebPush/Create/index.scss +7 -0
- package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
- package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
package/constants/unified.js
CHANGED
|
@@ -160,6 +160,24 @@ 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
|
+
|
|
163
181
|
export const GET_TRANSLATION_MAPPED = {
|
|
164
182
|
'en': 'en-US',
|
|
165
183
|
'zh-cn': 'zh',
|
|
@@ -197,3 +215,14 @@ export const LOGOUT_FAILURE = 'cap/LOGOUT_FAILURE';
|
|
|
197
215
|
export const JAPANESE_HELP_TEXT = 'ヘルプ :トークンの定義';
|
|
198
216
|
|
|
199
217
|
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
|
@@ -89,21 +89,26 @@ 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;
|
|
92
93
|
global.encodeURIComponent = jest.fn(() => { throw new Error('encodeURIComponent error'); });
|
|
93
94
|
const blob = new Blob([''], { type: 'image/jpeg' });
|
|
94
95
|
const file = new File([blob], '@%test.jpeg', { type: 'image/jpeg' });
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
}
|
|
107
112
|
});
|
|
108
113
|
});
|
|
109
114
|
|
|
@@ -1037,16 +1042,26 @@ describe('getMembersLookup', () => {
|
|
|
1037
1042
|
expect(result).toBeInstanceOf(Promise);
|
|
1038
1043
|
});
|
|
1039
1044
|
|
|
1040
|
-
it('should call fetch with correct URL encoding and GET method', () => {
|
|
1045
|
+
it('should call fetch with correct URL encoding and GET method', async () => {
|
|
1041
1046
|
global.fetch.mockClear();
|
|
1042
|
-
getMembersLookup('email', 'user+test@example.com');
|
|
1047
|
+
await getMembersLookup('email', 'user+test@example.com');
|
|
1043
1048
|
expect(global.fetch).toHaveBeenCalled();
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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');
|
|
1050
1065
|
});
|
|
1051
1066
|
});
|
|
1052
1067
|
|
|
@@ -422,14 +422,86 @@ export function removeAllCdnLocalStorageItems() {
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
/**
|
|
425
|
-
*
|
|
426
|
-
*
|
|
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
|
+
// Parse and validate S3_CDN_MAP before proceeding; malformed or empty map
|
|
466
|
+
// means CDN lookups would silently fail, so fall back to the API instead.
|
|
467
|
+
const parsedS3CdnMap = safeJsonParse(env.S3_CDN_MAP, null);
|
|
468
|
+
if (
|
|
469
|
+
!parsedS3CdnMap
|
|
470
|
+
|| typeof parsedS3CdnMap !== 'object'
|
|
471
|
+
|| Array.isArray(parsedS3CdnMap)
|
|
472
|
+
|| !Object.keys(parsedS3CdnMap).length
|
|
473
|
+
) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
saveCdnConfigs({
|
|
478
|
+
hostname: env.CDN_HOSTNAME,
|
|
479
|
+
transformationUrlSuffix: env.CDN_IMG_TRANSFORMATION_URL_SUFFIX,
|
|
480
|
+
bucketPath: env.CREATIVE_ASSETS_BUCKET_PATH,
|
|
481
|
+
qualityCfg: safeJsonParse(env.CDN_IMG_QUALITY_CFG, {}),
|
|
482
|
+
overrideEmailQuality: env.OVERRIDE_IMAGE_QUALITY === 'true',
|
|
483
|
+
overrideEmailQualityMapping: safeJsonParse(env.IMAGE_QUALITY_MAPPING, []),
|
|
484
|
+
s3CdnMap: parsedS3CdnMap,
|
|
485
|
+
});
|
|
486
|
+
return true;
|
|
487
|
+
} catch (e) {
|
|
488
|
+
Bugsnag.leaveBreadcrumb('initCdnConfigFromEnv failed to apply window.APP_ENV');
|
|
489
|
+
Bugsnag.notify(e, (event) => {
|
|
490
|
+
event.severity = 'error';
|
|
491
|
+
});
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
*
|
|
498
|
+
* @param {*} configsResponse
|
|
427
499
|
* This util function saves the getCdnConfigs response into the local storage.
|
|
428
500
|
* The following response items are mapped into the following keys in local storage:
|
|
429
501
|
* hostname -> CREATIVES_CDN_BASE_URL
|
|
430
502
|
* qualityCfg ->CREATIVES_CDN_QUALITY_CONFIG
|
|
431
503
|
* transformationUrlSuffix -> CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX
|
|
432
|
-
*
|
|
504
|
+
*
|
|
433
505
|
* 1. If configsReponse is empty. All above mentioned keys are deleted from localstorage.
|
|
434
506
|
* 2. If any one of the above keys is missing. The respective keys are deleted from localstorage.
|
|
435
507
|
* 3. Else it is saved into the localstorage.
|
package/utils/commonUtils.js
CHANGED
|
@@ -539,4 +539,22 @@ 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
|
-
};
|
|
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
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import isEmpty from 'lodash/isEmpty';
|
|
2
|
+
import { RCS_API_SMS_FALLBACK_KEYS } from '../constants/unified';
|
|
3
|
+
import {
|
|
4
|
+
COMBINED_SMS_TEMPLATE_VAR_REGEX,
|
|
5
|
+
isAnyTemplateVarToken,
|
|
6
|
+
splitTemplateVarString,
|
|
7
|
+
} from './templateVarUtils';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
MESSAGE,
|
|
11
|
+
TEMPLATE_CONFIGS,
|
|
12
|
+
RCS_SMS_FALLBACK_VAR_MAPPED,
|
|
13
|
+
} = RCS_API_SMS_FALLBACK_KEYS;
|
|
14
|
+
|
|
15
|
+
function mergeSmsFallbackSlots(templateStr, varMapped) {
|
|
16
|
+
const map = varMapped != null && typeof varMapped === 'object' ? varMapped : {};
|
|
17
|
+
if (!Object.keys(map).length) return typeof templateStr === 'string' ? templateStr : '';
|
|
18
|
+
const str = typeof templateStr === 'string' ? templateStr : '';
|
|
19
|
+
return splitTemplateVarString(str, COMBINED_SMS_TEMPLATE_VAR_REGEX)
|
|
20
|
+
.map((seg, i) => {
|
|
21
|
+
if (!isAnyTemplateVarToken(seg)) return seg;
|
|
22
|
+
const key = `${seg}_${i}`;
|
|
23
|
+
if (Object.prototype.hasOwnProperty.call(map, key)) {
|
|
24
|
+
const v = map[key];
|
|
25
|
+
return v == null ? '' : String(v);
|
|
26
|
+
}
|
|
27
|
+
return seg;
|
|
28
|
+
})
|
|
29
|
+
.join('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Mutates one RCS messageContent entry: strips UI-only fields, promotes smsFallBackContent to a
|
|
34
|
+
* sibling of rcsContent, and folds rcsSmsFallbackVarMapped into the message string for the API.
|
|
35
|
+
*/
|
|
36
|
+
export const normalizeRcsMessageContentForApi = messageContentItem => {
|
|
37
|
+
const {
|
|
38
|
+
rcsContent = {},
|
|
39
|
+
smsFallBackContent: rootSmsFallbackContent = {},
|
|
40
|
+
} = messageContentItem;
|
|
41
|
+
const {
|
|
42
|
+
smsFallBackContent: legacySmsFallbackNestedUnderCard,
|
|
43
|
+
...rcsCardPayloadOnly
|
|
44
|
+
} = { ...rcsContent };
|
|
45
|
+
/* Legacy nested first, then root — so current root `smsFallBackContent` wins on key clashes. */
|
|
46
|
+
const mergedSmsFallbackSources = {
|
|
47
|
+
...(legacySmsFallbackNestedUnderCard ?? {}),
|
|
48
|
+
...rootSmsFallbackContent,
|
|
49
|
+
};
|
|
50
|
+
messageContentItem.rcsContent = rcsCardPayloadOnly;
|
|
51
|
+
|
|
52
|
+
delete messageContentItem.templateConfigs;
|
|
53
|
+
delete rcsCardPayloadOnly.templateConfigs;
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(rcsCardPayloadOnly.cardContent)) {
|
|
56
|
+
rcsCardPayloadOnly.cardContent.forEach(card => {
|
|
57
|
+
if (Array.isArray(card.suggestions)) {
|
|
58
|
+
card.suggestions.forEach(suggestion => {
|
|
59
|
+
delete suggestion.isSaved;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const varMappedMerged = mergedSmsFallbackSources[RCS_SMS_FALLBACK_VAR_MAPPED];
|
|
66
|
+
const hasVarMapped =
|
|
67
|
+
varMappedMerged != null
|
|
68
|
+
&& typeof varMappedMerged === 'object'
|
|
69
|
+
&& Object.keys(varMappedMerged).length > 0;
|
|
70
|
+
const hasMessageKey = Object.prototype.hasOwnProperty.call(mergedSmsFallbackSources, MESSAGE);
|
|
71
|
+
const rawMessage = hasMessageKey ? mergedSmsFallbackSources[MESSAGE] : undefined;
|
|
72
|
+
const messageForApi =
|
|
73
|
+
hasMessageKey && hasVarMapped
|
|
74
|
+
? mergeSmsFallbackSlots(rawMessage == null ? '' : String(rawMessage), varMappedMerged)
|
|
75
|
+
: rawMessage;
|
|
76
|
+
|
|
77
|
+
const apiSiblingSmsFallback = Object.fromEntries(
|
|
78
|
+
[
|
|
79
|
+
hasMessageKey && [MESSAGE, messageForApi],
|
|
80
|
+
!isEmpty(mergedSmsFallbackSources[TEMPLATE_CONFIGS]) && [
|
|
81
|
+
TEMPLATE_CONFIGS,
|
|
82
|
+
mergedSmsFallbackSources[TEMPLATE_CONFIGS],
|
|
83
|
+
],
|
|
84
|
+
].filter(Boolean),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (Object.keys(apiSiblingSmsFallback).length) {
|
|
88
|
+
messageContentItem.smsFallBackContent = apiSiblingSmsFallback;
|
|
89
|
+
} else {
|
|
90
|
+
delete messageContentItem.smsFallBackContent;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for templates containing {{var}} and DLT `{#var#}` tokens.
|
|
3
|
+
* Same split process used by WhatsApp/RCS: match vars with regex, then split content at each var.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
COMBINED_SMS_TEMPLATE_VAR_REGEX,
|
|
8
|
+
DEFAULT_MUSTACHE_VAR_REGEX,
|
|
9
|
+
DLT_HASH_VAR_TOKEN_FULL_STRING_REGEX,
|
|
10
|
+
DLT_HASH_TOKEN_INNER_NAME_REGEX,
|
|
11
|
+
DLT_VAR_BODY_CAPTURE_REGEX,
|
|
12
|
+
MUSTACHE_TOKEN_INNER_NAME_REGEX,
|
|
13
|
+
MUSTACHE_VAR_NAME_CAPTURE_REGEX,
|
|
14
|
+
MUSTACHE_VAR_TOKEN_FULL_STRING_REGEX,
|
|
15
|
+
} from '../constants/unified';
|
|
16
|
+
|
|
17
|
+
export { COMBINED_SMS_TEMPLATE_VAR_REGEX, DEFAULT_MUSTACHE_VAR_REGEX } from '../constants/unified';
|
|
18
|
+
|
|
19
|
+
const isMustacheVarToken = (s) =>
|
|
20
|
+
typeof s === 'string' && MUSTACHE_VAR_TOKEN_FULL_STRING_REGEX.test(s);
|
|
21
|
+
|
|
22
|
+
export const isDltHashVarToken = (s) =>
|
|
23
|
+
typeof s === 'string' && DLT_HASH_VAR_TOKEN_FULL_STRING_REGEX.test(s);
|
|
24
|
+
|
|
25
|
+
export const isAnyTemplateVarToken = (s) => isMustacheVarToken(s) || isDltHashVarToken(s);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `RegExp.prototype.exec` only advances `lastIndex` when the `g` and/or `y` flag is set.
|
|
29
|
+
* A non-global regex in a `while ((m = re.exec(s)) !== null)` loop never advances and can run forever.
|
|
30
|
+
*
|
|
31
|
+
* @param {RegExp} regex
|
|
32
|
+
* @returns {RegExp} Same instance if already global; otherwise a new RegExp with `g` appended to flags.
|
|
33
|
+
*/
|
|
34
|
+
function ensureGlobalRegexForExecLoop(regex) {
|
|
35
|
+
if (!regex || !(regex instanceof RegExp) || regex.global) {
|
|
36
|
+
return regex;
|
|
37
|
+
}
|
|
38
|
+
return new RegExp(regex.source, `${regex.flags}g`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Splits `content` into alternating plain-text segments and variable tokens, using the order of
|
|
43
|
+
* matches in `matchedVariableTokens` (e.g. from `String.prototype.match` with a global regex).
|
|
44
|
+
*
|
|
45
|
+
* @param {string[]} matchedVariableTokens - Matched tokens in left-to-right order
|
|
46
|
+
* @param {string} content - Full template string
|
|
47
|
+
* @returns {string[]}
|
|
48
|
+
*/
|
|
49
|
+
export const splitContentByOrderedVarTokens = (matchedVariableTokens, content) => {
|
|
50
|
+
const segmentList = [];
|
|
51
|
+
const tokenQueue = [...(matchedVariableTokens ?? [])];
|
|
52
|
+
let remainder = content ?? '';
|
|
53
|
+
while ((remainder?.length ?? 0) > 0) {
|
|
54
|
+
const nextVarToken = tokenQueue?.[0];
|
|
55
|
+
if (nextVarToken == null || nextVarToken === '') {
|
|
56
|
+
segmentList.push(remainder);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
const varStartIndex = remainder.indexOf(nextVarToken);
|
|
60
|
+
if (varStartIndex !== -1) {
|
|
61
|
+
segmentList.push(remainder.substring(0, varStartIndex));
|
|
62
|
+
segmentList.push(nextVarToken);
|
|
63
|
+
const afterVar = varStartIndex + (nextVarToken?.length ?? 0);
|
|
64
|
+
remainder = remainder.substring(afterVar, remainder?.length ?? 0);
|
|
65
|
+
tokenQueue.shift();
|
|
66
|
+
} else {
|
|
67
|
+
segmentList.push(remainder);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return segmentList;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Splits a template string into an array of text + {{var}} segments using the given regex.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} str - Template string
|
|
78
|
+
* @param {RegExp} [varRegex] - Regex that matches var tokens; defaults to DEFAULT_MUSTACHE_VAR_REGEX
|
|
79
|
+
* @returns {string[]}
|
|
80
|
+
*/
|
|
81
|
+
export const splitTemplateVarString = (str = '', varRegex = DEFAULT_MUSTACHE_VAR_REGEX) => {
|
|
82
|
+
if (!str) return [];
|
|
83
|
+
const matchedVariableTokens = str.match(varRegex) || [];
|
|
84
|
+
return splitContentByOrderedVarTokens(matchedVariableTokens, str).filter(
|
|
85
|
+
(segment) => segment !== ''
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extracts unique variable names from `{{var}}` (and, when using default capture, `{#var#}`) strings.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} templateStr
|
|
93
|
+
* @param {RegExp} [captureRegex] - regex with a single capture group for the var name; if omitted, also scans DLT `{#…#}`.
|
|
94
|
+
* @returns {string[]}
|
|
95
|
+
*/
|
|
96
|
+
export const extractTemplateVariables = (templateStr = '', captureRegex) => {
|
|
97
|
+
if (!templateStr) return [];
|
|
98
|
+
const variables = [];
|
|
99
|
+
const add = (name) => {
|
|
100
|
+
const n = (name || '').trim();
|
|
101
|
+
if (n && !variables.includes(n)) variables.push(n);
|
|
102
|
+
};
|
|
103
|
+
const mustacheRe = ensureGlobalRegexForExecLoop(
|
|
104
|
+
captureRegex || MUSTACHE_VAR_NAME_CAPTURE_REGEX,
|
|
105
|
+
);
|
|
106
|
+
let match;
|
|
107
|
+
while ((match = mustacheRe.exec(templateStr)) !== null) {
|
|
108
|
+
add(match?.[1]);
|
|
109
|
+
}
|
|
110
|
+
if (!captureRegex) {
|
|
111
|
+
const dltRe = ensureGlobalRegexForExecLoop(DLT_VAR_BODY_CAPTURE_REGEX);
|
|
112
|
+
let dltMatch;
|
|
113
|
+
while ((dltMatch = dltRe.exec(templateStr)) !== null) {
|
|
114
|
+
add(dltMatch?.[1] || 'var');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return variables;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Looks up the inner name of a `{{name}}` or `{#name#}` token in a flat key→value map.
|
|
122
|
+
* Handles both exact matches and dot-path suffixes (e.g. `tag.FORMAT_1` → name `FORMAT_1`).
|
|
123
|
+
* Returns the resolved string value, or `undefined` if not found / blank.
|
|
124
|
+
*/
|
|
125
|
+
function resolveUserVarForToken(segment, userVarMap) {
|
|
126
|
+
if (!userVarMap || typeof userVarMap !== 'object') return undefined;
|
|
127
|
+
const mMustache = segment.match(MUSTACHE_TOKEN_INNER_NAME_REGEX);
|
|
128
|
+
const mDlt = segment.match(DLT_HASH_TOKEN_INNER_NAME_REGEX);
|
|
129
|
+
const innerName = ((mMustache ? mMustache[1] : (mDlt ? mDlt[1] : '')) ?? '').trim();
|
|
130
|
+
if (!innerName) return undefined;
|
|
131
|
+
const direct = userVarMap[innerName];
|
|
132
|
+
if (direct != null && String(direct).trim() !== '') return String(direct);
|
|
133
|
+
const hit = Object.keys(userVarMap).find((k) => k === innerName || k.endsWith(`.${innerName}`));
|
|
134
|
+
if (hit != null && userVarMap[hit] != null && String(userVarMap[hit]).trim() !== '') return String(userVarMap[hit]);
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* SMS / DLT template preview: replace `{{…}}` / `{#…#}` tokens using `varMapData` keys `${token}_${index}`.
|
|
140
|
+
* Used by SmsTraiEdit (RCS SMS fallback) and UnifiedPreview fallback SMS bubble.
|
|
141
|
+
* DLT `{#…#}`: empty / unset slot values show the raw token (matches DLT preview UX); mustache `{{…}}` still
|
|
142
|
+
* resolves to empty when the slot is cleared.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} templateStr
|
|
145
|
+
* @param {Object} varMapData - Slot map (`${token}_${index}` → value)
|
|
146
|
+
* @param {Object|null} [userVarMap] - Optional Test & Preview custom values; used as fallback when a slot
|
|
147
|
+
* is absent or blank. When omitted the function behaves identically to its original two-argument form.
|
|
148
|
+
*/
|
|
149
|
+
export const getFallbackResolvedContent = (templateStr = '', varMapData = {}, userVarMap = null) => {
|
|
150
|
+
const fallbackVarSlotMap = varMapData ?? {};
|
|
151
|
+
const templateSegments = splitTemplateVarString(templateStr, COMBINED_SMS_TEMPLATE_VAR_REGEX);
|
|
152
|
+
return templateSegments
|
|
153
|
+
.map((segment, segmentIndex) => {
|
|
154
|
+
const isVariableToken = typeof segment === 'string' && isAnyTemplateVarToken(segment);
|
|
155
|
+
if (!isVariableToken) return segment;
|
|
156
|
+
const slotKey = `${segment}_${segmentIndex}`;
|
|
157
|
+
if (Object.prototype.hasOwnProperty.call(fallbackVarSlotMap, slotKey)) {
|
|
158
|
+
const slotValue = fallbackVarSlotMap[slotKey];
|
|
159
|
+
if (isDltHashVarToken(segment)) {
|
|
160
|
+
if (slotValue == null || String(slotValue).trim() === '') {
|
|
161
|
+
if (userVarMap) { const uv = resolveUserVarForToken(segment, userVarMap); if (uv !== undefined) return uv; }
|
|
162
|
+
return segment;
|
|
163
|
+
}
|
|
164
|
+
return String(slotValue);
|
|
165
|
+
}
|
|
166
|
+
if (slotValue != null && String(slotValue).trim() !== '') return String(slotValue);
|
|
167
|
+
if (userVarMap) { const uv = resolveUserVarForToken(segment, userVarMap); if (uv !== undefined) return uv; }
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
if (userVarMap) { const uv = resolveUserVarForToken(segment, userVarMap); if (uv !== undefined) return uv; }
|
|
171
|
+
if (isDltHashVarToken(segment)) return segment;
|
|
172
|
+
return '';
|
|
173
|
+
})
|
|
174
|
+
.join('');
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* SMS fallback **card** (library list): show filled slot text; if a slot is empty, unset, or
|
|
179
|
+
* whitespace-only, show the raw `{{…}}` / `{#…#}` token so “save without labels” still shows
|
|
180
|
+
* `{#var#}` in the card. {@link getFallbackResolvedContent} keeps DLT placeholders in preview when
|
|
181
|
+
* slots are empty; mustache cleared slots can still render empty.
|
|
182
|
+
*/
|
|
183
|
+
export const getFallbackResolvedContentForCardDisplay = (templateStr = '', varMapData = {}) => {
|
|
184
|
+
const fallbackVarSlotMap = varMapData ?? {};
|
|
185
|
+
const templateSegments = splitTemplateVarString(templateStr, COMBINED_SMS_TEMPLATE_VAR_REGEX);
|
|
186
|
+
return templateSegments
|
|
187
|
+
.map((segment, segmentIndex) => {
|
|
188
|
+
const isVariableToken = typeof segment === 'string' && isAnyTemplateVarToken(segment);
|
|
189
|
+
if (!isVariableToken) return segment;
|
|
190
|
+
const slotKey = `${segment}_${segmentIndex}`;
|
|
191
|
+
if (Object.prototype.hasOwnProperty.call(fallbackVarSlotMap, slotKey)) {
|
|
192
|
+
const slotValue = fallbackVarSlotMap[slotKey];
|
|
193
|
+
if (slotValue == null) return segment;
|
|
194
|
+
const str = String(slotValue);
|
|
195
|
+
if (str.trim() === '') return segment;
|
|
196
|
+
return str;
|
|
197
|
+
}
|
|
198
|
+
return segment;
|
|
199
|
+
})
|
|
200
|
+
.join('');
|
|
201
|
+
};
|
|
@@ -546,6 +546,133 @@ 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 optional JSON env vars (qualityCfg, mapping)", () => {
|
|
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: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
|
|
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("returns false when S3_CDN_MAP is malformed JSON", () => {
|
|
629
|
+
window.APP_ENV = { ...completeEnv, S3_CDN_MAP: "}{" };
|
|
630
|
+
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it.each([
|
|
634
|
+
["empty object", "{}"],
|
|
635
|
+
["array", '["a","b"]'],
|
|
636
|
+
["null", "null"],
|
|
637
|
+
["number", "42"],
|
|
638
|
+
["string", '"foo"'],
|
|
639
|
+
])("returns false when S3_CDN_MAP parses to %s (not a non-empty object)", (_label, value) => {
|
|
640
|
+
window.APP_ENV = { ...completeEnv, S3_CDN_MAP: value };
|
|
641
|
+
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("treats OVERRIDE_IMAGE_QUALITY values other than the string \"true\" as false", () => {
|
|
645
|
+
window.APP_ENV = {
|
|
646
|
+
CDN_HOSTNAME: "https://storage.crm.n.content-cdn.io",
|
|
647
|
+
CDN_IMG_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
|
|
648
|
+
CREATIVE_ASSETS_BUCKET_PATH: "intouch_creative_assets",
|
|
649
|
+
OVERRIDE_IMAGE_QUALITY: "no",
|
|
650
|
+
CDN_IMG_QUALITY_CFG: "{}",
|
|
651
|
+
IMAGE_QUALITY_MAPPING: "[]",
|
|
652
|
+
S3_CDN_MAP: '{"host.s3.amazonaws.com":{"cdn_host":"cdn.example.com","bucket_path":"x"}}',
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
cdnUtils.initCdnConfigFromEnv();
|
|
656
|
+
expect(localStorage.getItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY")).toBe(undefined);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it("returns false and notifies Bugsnag when window.APP_ENV access throws", () => {
|
|
660
|
+
// Force the `window.APP_ENV` read inside initCdnConfigFromEnv to throw,
|
|
661
|
+
// which exercises the function's outer catch block.
|
|
662
|
+
Object.defineProperty(window, "APP_ENV", {
|
|
663
|
+
get() { throw new Error("APP_ENV access boom"); },
|
|
664
|
+
configurable: true,
|
|
665
|
+
});
|
|
666
|
+
const breadcrumbSpy = jest.spyOn(Bugsnag, "leaveBreadcrumb");
|
|
667
|
+
|
|
668
|
+
expect(cdnUtils.initCdnConfigFromEnv()).toBe(false);
|
|
669
|
+
expect(breadcrumbSpy).toHaveBeenCalledWith(
|
|
670
|
+
"initCdnConfigFromEnv failed to apply window.APP_ENV"
|
|
671
|
+
);
|
|
672
|
+
expect(bugsnagSpy).toHaveBeenCalled();
|
|
673
|
+
});
|
|
674
|
+
});
|
|
675
|
+
|
|
549
676
|
describe("getEmailImageOverrideQuality()",()=>{
|
|
550
677
|
it("Should return quality of last element in array if file size is greater than max provided in array",()=>{
|
|
551
678
|
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|