@capillarytech/creatives-library 8.0.113 → 8.0.114-alpha.0
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/containers/App/test/saga.test.js +1 -1
- package/containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
- package/containers/Assets/Gallery/tests/actions.test.js +2 -3
- package/containers/Assets/Gallery/tests/reducer.test.js +7 -7
- package/containers/Assets/Gallery/tests/saga.test.js +9 -9
- package/containers/Dashboard/test/saga.test.js +1 -1
- package/containers/Ebill/test/saga.test.js +1 -1
- package/containers/Email/test/saga.test.js +33 -33
- package/containers/LanguageProvider/tests/actions.test.js +1 -1
- package/containers/LanguageProvider/tests/reducer.test.js +2 -2
- package/containers/LanguageProvider/tests/selectors.test.js +1 -1
- package/containers/Line/Create/tests/saga.test.js +2 -9
- package/containers/Line/Edit/test/saga.test.js +10 -14
- package/containers/MobilePush/Create/test/saga.test.js +2 -2
- package/containers/MobilePush/Edit/tests/saga.test.js +14 -14
- package/containers/Sms/Create/test/saga.test.js +4 -5
- package/containers/Sms/Edit/test/saga.test.js +1 -1
- package/containers/Templates/test/saga.test.js +14 -17
- package/containers/WeChat/MapTemplates/test/saga.test.js +9 -13
- package/containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
- package/containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
- package/package.json +1 -1
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -9
- package/utils/commonUtils.js +389 -3
- package/utils/tagValidations.js +20 -5
- package/utils/tests/authWrapper.test.js +2 -2
- package/utils/tests/cdnTransformation.test.js +16 -15
- package/utils/tests/commonUtil.test.js +324 -178
- package/v2Components/CapVideoUpload/tests/index.test.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +1 -2
- package/v2Components/ErrorInfoNote/ErrorTypeRenderer.js +130 -0
- package/v2Components/ErrorInfoNote/ErrorTypeRenderer.test.js +146 -0
- package/v2Components/ErrorInfoNote/index.js +114 -46
- package/v2Components/ErrorInfoNote/messages.js +25 -0
- package/v2Components/ErrorInfoNote/style.scss +14 -1
- package/v2Components/ErrorInfoNote/utils.js +28 -0
- package/v2Components/ErrorInfoNote/utils.test.js +93 -0
- package/v2Components/FormBuilder/index.js +200 -127
- package/v2Components/FormBuilder/messages.js +1 -1
- package/v2Components/MarketingObjective/test/index.test.js +1 -1
- package/v2Components/NavigationBar/tests/saga.test.js +2 -3
- package/v2Containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
- package/v2Containers/Assets/Gallery/tests/actions.test.js +2 -3
- package/v2Containers/Assets/Gallery/tests/reducer.test.js +7 -7
- package/v2Containers/Assets/Gallery/tests/saga.test.js +2 -2
- package/v2Containers/BeeEditor/test/saga.test.js +1 -1
- package/v2Containers/CallTask/test/saga.test.js +1 -1
- package/v2Containers/Cap/reducer.js +4 -4
- package/v2Containers/Cap/tests/actions.test.js +1 -1
- package/v2Containers/Cap/tests/reducer.test.js +11 -11
- package/v2Containers/Cap/tests/saga.test.js +1 -1
- package/v2Containers/Cap/tests/selectors.test.js +3 -3
- package/v2Containers/CapFacebookPreview/tests/saga.test.js +1 -1
- package/v2Containers/CreativesContainer/SlideBoxContent.js +23 -3
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
- package/v2Containers/CreativesContainer/constants.js +2 -1
- package/v2Containers/CreativesContainer/index.js +37 -10
- package/v2Containers/CreativesContainer/messages.js +4 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +21 -3
- package/v2Containers/Ebill/index.js +3 -3
- package/v2Containers/Ebill/test/saga.test.js +1 -1
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -4
- package/v2Containers/Email/tests/actions.test.js +1 -1
- package/v2Containers/Email/tests/reducer.test.js +2 -2
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +1 -1
- package/v2Containers/FTP/test/saga.test.js +1 -1
- package/v2Containers/Facebook/test/saga.test.js +7 -7
- package/v2Containers/InApp/index.js +127 -49
- package/v2Containers/InApp/tests/action.test.js +7 -7
- package/v2Containers/InApp/tests/index.test.js +2 -4
- package/v2Containers/InApp/tests/reducer.test.js +175 -89
- package/v2Containers/InApp/tests/sagas.test.js +1 -1
- package/v2Containers/InApp/utils.js +37 -0
- package/v2Containers/LanguageProvider/tests/actions.test.js +1 -1
- package/v2Containers/LanguageProvider/tests/reducer.test.js +3 -3
- package/v2Containers/LanguageProvider/tests/saga.test.js +2 -2
- package/v2Containers/LanguageProvider/tests/selectors.test.js +1 -1
- package/v2Containers/Line/Container/ImageCarousel/tests/utils.test.js +3 -3
- package/v2Containers/Line/Container/Sticker/tests/utils.test.js +6 -6
- package/v2Containers/MobilePush/Create/index.js +24 -20
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/MobilePush/Edit/index.js +4 -1
- package/v2Containers/MobilePush/Edit/test/saga.test.js +14 -14
- package/v2Containers/MobilepushWrapper/index.js +2 -0
- package/v2Containers/Rcs/tests/saga.test.js +1 -1
- package/v2Containers/Sms/Create/index.js +14 -2
- package/v2Containers/Sms/Create/test/saga.test.js +1 -1
- package/v2Containers/Sms/Edit/index.js +2 -0
- package/v2Containers/Sms/Edit/test/saga.test.js +5 -5
- package/v2Containers/SmsTrai/Create/tests/saga.test.js +1 -1
- package/v2Containers/SmsTrai/Create/tests/selectors.test.js +1 -1
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TagList/tests/TagList.test.js +1 -3
- package/v2Containers/TagList/tests/utils.test.js +3 -3
- package/v2Containers/Templates/tests/actions.test.js +1 -1
- package/v2Containers/Templates/tests/reducer.test.js +8 -8
- package/v2Containers/Templates/tests/sagas.test.js +2 -4
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -13
- package/v2Containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
- package/v2Containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
- package/v2Containers/Whatsapp/tests/__snapshots__/utils.test.js.snap +9 -9
- package/v2Containers/Whatsapp/tests/actions.test.js +3 -3
- package/v2Containers/Whatsapp/tests/reducer.test.js +32 -36
- package/v2Containers/Whatsapp/tests/utils.test.js +19 -19
- package/v2Containers/Zalo/tests/actions.test.js +3 -3
- package/v2Containers/Zalo/tests/reducer.test.js +72 -42
package/utils/commonUtils.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FormattedMessage } from 'react-intl';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import { convert } from "html-to-text";
|
|
5
|
+
import { EMBEDDED } from "../v2Containers/Whatsapp/constants";
|
|
6
|
+
import {
|
|
7
|
+
EDIT,
|
|
8
|
+
EMAIL,
|
|
9
|
+
INAPP,
|
|
10
|
+
PREVIEW,
|
|
11
|
+
} from "../v2Containers/App/constants";
|
|
12
|
+
import {
|
|
13
|
+
MOBILE_PUSH,
|
|
14
|
+
SMS,
|
|
15
|
+
ANDROID,
|
|
16
|
+
IOS,
|
|
17
|
+
} from "../v2Containers/CreativesContainer/constants";
|
|
18
|
+
import { GLOBAL_CONVERT_OPTIONS } from "../v2Components/FormBuilder/constants";
|
|
19
|
+
import { checkSupport, extractNames } from "./tagValidations";
|
|
7
20
|
export const apiMessageFormatHandler = (id, fallback) => (
|
|
8
21
|
<FormattedMessage id={id} defaultMessage={fallback} />
|
|
9
22
|
);
|
|
@@ -83,3 +96,376 @@ export const transformCustomFieldsData = (customFields) => {
|
|
|
83
96
|
};
|
|
84
97
|
|
|
85
98
|
export const isTagIncluded = (value) => value && value.includes('{{') && value.includes('}}');
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
* @param {*} channel
|
|
104
|
+
* @param {*} formData
|
|
105
|
+
* @returns
|
|
106
|
+
*/
|
|
107
|
+
export const getChannelData = (channel, formData) => {
|
|
108
|
+
switch (channel?.toUpperCase()) {
|
|
109
|
+
case EMAIL.toUpperCase():
|
|
110
|
+
return convert(
|
|
111
|
+
formData?.base?.en?.["template-content"] || "",
|
|
112
|
+
GLOBAL_CONVERT_OPTIONS
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
case SMS:
|
|
116
|
+
return (
|
|
117
|
+
`${formData?.base?.["sms-editor"]} ${formData?.["template-name"]}` || ""
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
default:
|
|
121
|
+
return JSON.stringify(formData);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Validates liquid template content and handles errors
|
|
127
|
+
* @param {string} content - The content to validate
|
|
128
|
+
* @param {string} channel - Channel type (EMAIL, SMS, MOBILE_PUSH, etc.)
|
|
129
|
+
* @param {object} options - Additional options for validation
|
|
130
|
+
* @param {function} options.getLiquidTags - Function to call to get liquid tags
|
|
131
|
+
* @param {function} options.formatMessage - Intl formatMessage function for i18n
|
|
132
|
+
* @param {object} options.messages - Messages for i18n
|
|
133
|
+
* @param {function} options.onError - Callback when error occurs
|
|
134
|
+
* @param {function} options.onSuccess - Callback when validation succeeds
|
|
135
|
+
* @param {object} options.tagLookupMap - Map of supported tags
|
|
136
|
+
* @param {array} options.eventContextTags - Context-specific tags
|
|
137
|
+
* @param {boolean} options.isLiquidFlow - Whether liquid flow is enabled
|
|
138
|
+
* @param {object} options.forwardedTags - Any forwarded tags (for Loyalty module)
|
|
139
|
+
* @param {string} options.tabType - For MPUSH: 'android' or 'ios'
|
|
140
|
+
* @param {number} options.currentTab - For MPUSH: current active tab (1 for Android, 2 for iOS)
|
|
141
|
+
* @param {function} options.skipTags - Function to determine if tags should be skipped
|
|
142
|
+
* @returns {Promise} - Promise that resolves to validation result
|
|
143
|
+
*/
|
|
144
|
+
export const validateLiquidTemplateContent = async (
|
|
145
|
+
content,
|
|
146
|
+
channel,
|
|
147
|
+
options
|
|
148
|
+
) => {
|
|
149
|
+
const {
|
|
150
|
+
getLiquidTags,
|
|
151
|
+
formatMessage,
|
|
152
|
+
messages,
|
|
153
|
+
onError = () => {},
|
|
154
|
+
onSuccess = () => {},
|
|
155
|
+
tagLookupMap,
|
|
156
|
+
eventContextTags,
|
|
157
|
+
isLiquidFlow,
|
|
158
|
+
forwardedTags = {},
|
|
159
|
+
tabType,
|
|
160
|
+
skipTags = () => false,
|
|
161
|
+
} = options;
|
|
162
|
+
|
|
163
|
+
// Empty content check
|
|
164
|
+
if (!content || content.trim() === "") {
|
|
165
|
+
const errorMsg = formatMessage(messages.emailBodyEmptyError);
|
|
166
|
+
onError({
|
|
167
|
+
standardErrors: [errorMsg],
|
|
168
|
+
liquidErrors: [],
|
|
169
|
+
tabType,
|
|
170
|
+
});
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Await getLiquidTags as a promise if it doesn't already return one
|
|
175
|
+
const getLiquidTagsAsync = (inputContent) => new Promise((resolve) => {
|
|
176
|
+
getLiquidTags(inputContent, (result) => {
|
|
177
|
+
resolve(result);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const result = await getLiquidTagsAsync(content);
|
|
182
|
+
const validString = /\S/.test(content);
|
|
183
|
+
|
|
184
|
+
// Handle API errors or empty content
|
|
185
|
+
if (result?.errors?.length > 0 || !validString) {
|
|
186
|
+
let standardErrors = [];
|
|
187
|
+
if (!validString) {
|
|
188
|
+
const emptyBodyError = formatMessage(messages.emailBodyEmptyError);
|
|
189
|
+
standardErrors = [emptyBodyError];
|
|
190
|
+
}
|
|
191
|
+
let liquidErrors;
|
|
192
|
+
if (result && Array.isArray(result?.errors)) {
|
|
193
|
+
liquidErrors = result?.errors?.map((error) => {
|
|
194
|
+
const message = typeof error?.message === "string"
|
|
195
|
+
? error.message
|
|
196
|
+
: formatMessage(messages.somethingWentWrong);
|
|
197
|
+
return message;
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
const somethingWrongMsg = formatMessage(messages.somethingWentWrong);
|
|
201
|
+
liquidErrors = [somethingWrongMsg];
|
|
202
|
+
}
|
|
203
|
+
onError({
|
|
204
|
+
standardErrors,
|
|
205
|
+
liquidErrors,
|
|
206
|
+
tabType,
|
|
207
|
+
});
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Extract and validate tags
|
|
212
|
+
const extractedLiquidTags = extractNames(result?.data || []);
|
|
213
|
+
// Get supported tags
|
|
214
|
+
const supportedLiquidTags = checkSupport(
|
|
215
|
+
result,
|
|
216
|
+
tagLookupMap,
|
|
217
|
+
eventContextTags,
|
|
218
|
+
isLiquidFlow,
|
|
219
|
+
forwardedTags
|
|
220
|
+
);
|
|
221
|
+
// Find unsupported tags
|
|
222
|
+
const unsupportedLiquidTags = extractedLiquidTags?.filter(
|
|
223
|
+
(tag) => !supportedLiquidTags?.includes(tag) && !skipTags(tag)
|
|
224
|
+
);
|
|
225
|
+
// Handle unsupported tags
|
|
226
|
+
if (unsupportedLiquidTags?.length > 0) {
|
|
227
|
+
const errorMsg = formatMessage(messages.unsupportedTagsValidationError, {
|
|
228
|
+
unsupportedTags: unsupportedLiquidTags.join(", "),
|
|
229
|
+
});
|
|
230
|
+
onError({
|
|
231
|
+
standardErrors: [],
|
|
232
|
+
liquidErrors: [errorMsg],
|
|
233
|
+
tabType,
|
|
234
|
+
});
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
// All validations passed
|
|
238
|
+
onSuccess(content, tabType);
|
|
239
|
+
return true;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Internal helper function to validate platform-specific content
|
|
243
|
+
export const _validatePlatformSpecificContent = async (
|
|
244
|
+
androidContent,
|
|
245
|
+
iosContent,
|
|
246
|
+
channel,
|
|
247
|
+
options // Contains parent's onError and other options for validateLiquidTemplateContent
|
|
248
|
+
) => {
|
|
249
|
+
const {
|
|
250
|
+
onError: parentOnError, // The onError callback from the calling function (e.g., validateMobilePushContent)
|
|
251
|
+
...commonVltcOptions // Other options like getLiquidTags, formatMessage, messages, currentTab, etc.
|
|
252
|
+
} = options;
|
|
253
|
+
|
|
254
|
+
let isAndroidValid = !androidContent; // True if no Android content, hence valid by default
|
|
255
|
+
let isIosValid = !iosContent; // True if no iOS content, hence valid by default
|
|
256
|
+
|
|
257
|
+
// Initialize error structure with platform-specific categories
|
|
258
|
+
const aggregatedErrors = {
|
|
259
|
+
standardErrors: {
|
|
260
|
+
[ANDROID]: [],
|
|
261
|
+
[IOS]: [],
|
|
262
|
+
generic: [],
|
|
263
|
+
},
|
|
264
|
+
liquidErrors: {
|
|
265
|
+
[ANDROID]: [],
|
|
266
|
+
[IOS]: [],
|
|
267
|
+
generic: [],
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// This aggregator is passed to validateLiquidTemplateContent.
|
|
272
|
+
// It accumulates errors in the new structure and calls the parentOnError
|
|
273
|
+
const internalOnErrorAggregator = ({
|
|
274
|
+
standardErrors = [],
|
|
275
|
+
liquidErrors = [],
|
|
276
|
+
tabType: tabTypeFromVLTC,
|
|
277
|
+
}) => {
|
|
278
|
+
aggregatedErrors.standardErrors[tabTypeFromVLTC?.toUpperCase()].push(
|
|
279
|
+
...standardErrors
|
|
280
|
+
);
|
|
281
|
+
aggregatedErrors.liquidErrors[tabTypeFromVLTC?.toUpperCase()].push(
|
|
282
|
+
...liquidErrors
|
|
283
|
+
);
|
|
284
|
+
parentOnError(aggregatedErrors);
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Base options for calling validateLiquidTemplateContent
|
|
288
|
+
const vltcCallOptions = {
|
|
289
|
+
...commonVltcOptions,
|
|
290
|
+
onError: internalOnErrorAggregator,
|
|
291
|
+
onSuccess: () => {},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
if (androidContent) {
|
|
295
|
+
isAndroidValid = await validateLiquidTemplateContent(
|
|
296
|
+
androidContent,
|
|
297
|
+
channel,
|
|
298
|
+
{ ...vltcCallOptions, tabType: ANDROID }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (iosContent) {
|
|
303
|
+
isIosValid = await validateLiquidTemplateContent(
|
|
304
|
+
iosContent,
|
|
305
|
+
channel,
|
|
306
|
+
{ ...vltcCallOptions, tabType: IOS }
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
return isAndroidValid && isIosValid; // Overall success
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Validate Mobile Push content for both Android and iOS tabs
|
|
314
|
+
* @param {object} formData - Form data containing Android and iOS content
|
|
315
|
+
* @param {object} options - Options for validation
|
|
316
|
+
* @returns {Promise} - Promise that resolves when validation completes
|
|
317
|
+
*/
|
|
318
|
+
export const validateMobilePushContent = async (formData, options) => {
|
|
319
|
+
const {
|
|
320
|
+
currentTab,
|
|
321
|
+
onError, // This is the onError callback passed by the caller of validateMobilePushContent
|
|
322
|
+
onSuccess, // This is the FINAL success callback for MobilePush
|
|
323
|
+
...restOptions // Options for validateLiquidTemplateContent (getLiquidTags, formatMessage, etc.)
|
|
324
|
+
} = options;
|
|
325
|
+
// Clear previous errors by calling the passed onError
|
|
326
|
+
onError({ standardErrors: [], liquidErrors: [] });
|
|
327
|
+
|
|
328
|
+
const androidContent = JSON.stringify(formData?.[0]);
|
|
329
|
+
const iosContent = JSON.stringify(formData?.[1]);
|
|
330
|
+
// Pass the original 'onError' from this function's arguments,
|
|
331
|
+
// 'currentTab', and other relevant options to the helper.
|
|
332
|
+
const overallSuccess = await _validatePlatformSpecificContent(
|
|
333
|
+
androidContent,
|
|
334
|
+
iosContent,
|
|
335
|
+
MOBILE_PUSH,
|
|
336
|
+
{ ...restOptions, currentTab, onError }
|
|
337
|
+
);
|
|
338
|
+
if (overallSuccess) {
|
|
339
|
+
// Determine content to submit based on the *active* tab preference
|
|
340
|
+
let contentToSubmit = "";
|
|
341
|
+
let tabTypeToSubmit = "";
|
|
342
|
+
if (currentTab === 1 && androidContent) {
|
|
343
|
+
contentToSubmit = androidContent;
|
|
344
|
+
tabTypeToSubmit = "android";
|
|
345
|
+
} else if (currentTab === 2 && iosContent) {
|
|
346
|
+
contentToSubmit = iosContent;
|
|
347
|
+
tabTypeToSubmit = "ios";
|
|
348
|
+
} else if (androidContent) {
|
|
349
|
+
// Fallback
|
|
350
|
+
contentToSubmit = androidContent;
|
|
351
|
+
tabTypeToSubmit = "android";
|
|
352
|
+
} else if (iosContent) {
|
|
353
|
+
// Fallback
|
|
354
|
+
contentToSubmit = iosContent;
|
|
355
|
+
tabTypeToSubmit = "ios";
|
|
356
|
+
}
|
|
357
|
+
// Call the FINAL onSuccess only ONCE here
|
|
358
|
+
onSuccess(contentToSubmit, tabTypeToSubmit);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Validate INAPP content for both Android and iOS
|
|
366
|
+
* @param {object} formData - Form data containing Android and iOS content
|
|
367
|
+
* @param {object} options - Options for validation
|
|
368
|
+
* @returns {Promise} - Promise that resolves when validation completes
|
|
369
|
+
*/
|
|
370
|
+
export const validateInAppContent = async (formData, options) => {
|
|
371
|
+
const {
|
|
372
|
+
onError, // This is the onError callback passed by the caller of validateInAppContent
|
|
373
|
+
onSuccess, // FINAL success callback
|
|
374
|
+
...restOptions // Options for validateLiquidTemplateContent
|
|
375
|
+
} = options;
|
|
376
|
+
|
|
377
|
+
// Clear previous errors with new structure
|
|
378
|
+
onError({
|
|
379
|
+
standardErrors: {
|
|
380
|
+
[ANDROID]: [],
|
|
381
|
+
[IOS]: [],
|
|
382
|
+
generic: [],
|
|
383
|
+
},
|
|
384
|
+
liquidErrors: {
|
|
385
|
+
[ANDROID]: [],
|
|
386
|
+
[IOS]: [],
|
|
387
|
+
generic: [],
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
let androidContent = "";
|
|
392
|
+
let iosContent = "";
|
|
393
|
+
let parseError = null;
|
|
394
|
+
|
|
395
|
+
// Extract content
|
|
396
|
+
try {
|
|
397
|
+
const androidData = formData?.versions?.base?.content?.ANDROID;
|
|
398
|
+
if (androidData) {
|
|
399
|
+
androidContent = [
|
|
400
|
+
androidData.title,
|
|
401
|
+
androidData.message,
|
|
402
|
+
...(androidData.ctas?.map((cta) => cta.text || cta.actionLink) || []),
|
|
403
|
+
]
|
|
404
|
+
.filter(Boolean)
|
|
405
|
+
.join(" ");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const iosData = formData?.versions?.base?.content?.IOS;
|
|
409
|
+
if (iosData) {
|
|
410
|
+
iosContent = [
|
|
411
|
+
iosData.title,
|
|
412
|
+
iosData.message,
|
|
413
|
+
...(iosData.ctas?.map((cta) => cta.text || cta.actionLink) || []),
|
|
414
|
+
]
|
|
415
|
+
.filter(Boolean)
|
|
416
|
+
.join(" ");
|
|
417
|
+
}
|
|
418
|
+
} catch (error) {
|
|
419
|
+
parseError = error; // Capture parsing error
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Handle parsing errors immediately
|
|
423
|
+
if (parseError) {
|
|
424
|
+
onError({
|
|
425
|
+
standardErrors: {
|
|
426
|
+
generic: [
|
|
427
|
+
restOptions.formatMessage(restOptions.messages.somethingWentWrong),
|
|
428
|
+
],
|
|
429
|
+
[ANDROID]: [],
|
|
430
|
+
[IOS]: [],
|
|
431
|
+
},
|
|
432
|
+
liquidErrors: {
|
|
433
|
+
generic: [],
|
|
434
|
+
[ANDROID]: [],
|
|
435
|
+
[IOS]: [],
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Check if *any* content exists
|
|
442
|
+
if (parseError) {
|
|
443
|
+
onError((prevErrors) => ({
|
|
444
|
+
...prevErrors,
|
|
445
|
+
standardErrors: {
|
|
446
|
+
...prevErrors.standardErrors,
|
|
447
|
+
generic: [
|
|
448
|
+
restOptions.formatMessage(restOptions.messages.somethingWentWrong),
|
|
449
|
+
],
|
|
450
|
+
},
|
|
451
|
+
}));
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Pass the original 'onError' from this function's arguments
|
|
456
|
+
// and other relevant options to the helper.
|
|
457
|
+
const overallSuccess = await _validatePlatformSpecificContent(
|
|
458
|
+
androidContent,
|
|
459
|
+
iosContent,
|
|
460
|
+
INAPP,
|
|
461
|
+
{ ...restOptions, onError }
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
if (overallSuccess) {
|
|
465
|
+
// Call FINAL onSuccess ONCE
|
|
466
|
+
onSuccess();
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
// Errors already reported via the 'onError' callback passed to _validatePlatformSpecificContent
|
|
470
|
+
return false;
|
|
471
|
+
};
|
package/utils/tagValidations.js
CHANGED
|
@@ -115,6 +115,20 @@ export function extractNames(data) {
|
|
|
115
115
|
return names;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// Helper to check if a tag is inside a {% ... %} block
|
|
119
|
+
export const isInsideLiquidBlock = (content, tagIndex) => {
|
|
120
|
+
const blockRegex = /{%(.*?)%}/gs;
|
|
121
|
+
let match;
|
|
122
|
+
for (match = blockRegex.exec(content); match !== null; match = blockRegex.exec(content)) {
|
|
123
|
+
const blockStart = match.index;
|
|
124
|
+
const blockEnd = blockStart + match[0].length;
|
|
125
|
+
if (tagIndex >= blockStart && tagIndex < blockEnd) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
};
|
|
131
|
+
|
|
118
132
|
/**
|
|
119
133
|
* Validates the tags based on the provided parameters.
|
|
120
134
|
* @param {Object} params - The parameters for tag validation.
|
|
@@ -130,7 +144,6 @@ export const validateTags = ({
|
|
|
130
144
|
const tags = tagsParam;
|
|
131
145
|
const injectedTags = transformInjectedTags(injectedTagsParams);
|
|
132
146
|
let currentModule = location?.query?.module ? location?.query?.module : DEFAULT;
|
|
133
|
-
|
|
134
147
|
if (tagModule) {
|
|
135
148
|
currentModule = tagModule;
|
|
136
149
|
}
|
|
@@ -160,6 +173,7 @@ export const validateTags = ({
|
|
|
160
173
|
let match = regex.exec(content);
|
|
161
174
|
while (match !== null) {
|
|
162
175
|
const tagValue = match[0].substring(indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
|
|
176
|
+
const tagIndex = match?.index;
|
|
163
177
|
match = regex.exec(content);
|
|
164
178
|
let ifSupported = false;
|
|
165
179
|
lodashForEach(tags, (tag) => {
|
|
@@ -170,10 +184,10 @@ export const validateTags = ({
|
|
|
170
184
|
const ifSkipped = skipTags(tagValue);
|
|
171
185
|
if (ifSkipped) {
|
|
172
186
|
ifSupported = true;
|
|
173
|
-
|
|
187
|
+
const isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") !== -1;
|
|
174
188
|
if (isUnsubscribeSkipped) {
|
|
175
189
|
const missingTagIndex = response.missingTags.indexOf("unsubscribe");
|
|
176
|
-
if(missingTagIndex
|
|
190
|
+
if (missingTagIndex !== -1) {
|
|
177
191
|
response.missingTags.splice(missingTagIndex, 1);
|
|
178
192
|
}
|
|
179
193
|
}
|
|
@@ -185,11 +199,12 @@ export const validateTags = ({
|
|
|
185
199
|
}
|
|
186
200
|
});
|
|
187
201
|
ifSupported = ifSupported || checkIfSupportedTag(tagValue, injectedTags);
|
|
188
|
-
if
|
|
202
|
+
// Only add to unsupportedTags if not inside a {% ... %} block and does not contain a dot
|
|
203
|
+
if (!ifSupported && !isInsideLiquidBlock(content, tagIndex) && tagValue?.indexOf('.') === -1) {
|
|
189
204
|
response.unsupportedTags.push(tagValue);
|
|
190
205
|
response.valid = false;
|
|
191
206
|
}
|
|
192
|
-
if (response.unsupportedTags.length
|
|
207
|
+
if (response.unsupportedTags.length === 0 && response.missingTags.length === 0) {
|
|
193
208
|
response.valid = true;
|
|
194
209
|
}
|
|
195
210
|
}
|
|
@@ -76,7 +76,7 @@ describe("Authentication Redirects", () => {
|
|
|
76
76
|
|
|
77
77
|
await waitFor(() => {
|
|
78
78
|
expect(history.location.pathname).toEqual("/");
|
|
79
|
-
});
|
|
79
|
+
}, { timeout: 5000, interval: 100 });
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
|
|
@@ -94,7 +94,7 @@ describe("Authentication Redirects", () => {
|
|
|
94
94
|
|
|
95
95
|
await waitFor(() => {
|
|
96
96
|
expect(history.location.pathname).toEqual("/some-route");
|
|
97
|
-
});
|
|
97
|
+
}, { timeout: 5000, interval: 100 });
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import * as nodeHtmlParser from 'node-html-parser';
|
|
2
|
+
import Bugsnag from '@bugsnag/js';
|
|
1
3
|
import * as cdnUtils from "../cdnTransformation";
|
|
2
4
|
import * as mockdata from "./cdnTransformation.mockdata";
|
|
3
|
-
import * as nodeHtmlParser from 'node-html-parser'
|
|
4
|
-
import Bugsnag from '@bugsnag/js';
|
|
5
5
|
import * as cdnTransformationHandler from './cdnTransformation.handler.mockdata';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
jest.setTimeout(
|
|
7
|
+
let bugsnagSpy;
|
|
8
|
+
let localStorageMock;
|
|
9
|
+
jest.setTimeout(30000);
|
|
10
10
|
beforeEach(() => {
|
|
11
|
-
localStorageMock = (
|
|
12
|
-
|
|
11
|
+
localStorageMock = (() => {
|
|
12
|
+
let store = {
|
|
13
13
|
CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
|
|
14
14
|
CREATIVES_S3_BUCKET_PATH: "intouch_creative_assets",
|
|
15
15
|
CREATIVES_CDN_BASE_URL: "https://storage.crm.n.content-cdn.io",
|
|
@@ -18,16 +18,16 @@ beforeEach(() => {
|
|
|
18
18
|
S3_CDN_MAP: '{"crm-nightly-new-fileservice.s3.amazonaws.com":{"cdn_host":"storage.crm.n.content-cdn.io","bucket_path":"intouch_creative_assets"}, "crm-prod-new-fileservice.s3.amazonaws.com": { "cdn_host": "storage.crm.p.content-cdn.io", "bucket_path": "fileservice.p/intouch_creative_assets" }}',
|
|
19
19
|
};
|
|
20
20
|
return {
|
|
21
|
-
getItem
|
|
21
|
+
getItem(key) {
|
|
22
22
|
return store[key];
|
|
23
23
|
},
|
|
24
|
-
setItem
|
|
24
|
+
setItem(key, value) {
|
|
25
25
|
store[key] = value.toString();
|
|
26
26
|
},
|
|
27
|
-
clear
|
|
27
|
+
clear() {
|
|
28
28
|
store = {};
|
|
29
29
|
},
|
|
30
|
-
removeItem
|
|
30
|
+
removeItem(key) {
|
|
31
31
|
delete store[key];
|
|
32
32
|
},
|
|
33
33
|
};
|
|
@@ -225,20 +225,21 @@ describe("cdnTransformationTests", () => {
|
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
it("should call bugsnag when parse() throws some unexpected error.", async () => {
|
|
228
|
-
const
|
|
228
|
+
const parseSpy = jest.spyOn(nodeHtmlParser, 'parse').mockImplementation(() => {
|
|
229
229
|
throw new Error();
|
|
230
|
-
})
|
|
230
|
+
});
|
|
231
231
|
expect(await cdnUtils.updateImagesInHtml(mockdata.emailRawInput)).toEqual(mockdata.emailRawInput);
|
|
232
232
|
expect(bugsnagSpy).toHaveBeenCalled();
|
|
233
|
+
parseSpy.mockRestore();
|
|
233
234
|
});
|
|
234
235
|
|
|
235
|
-
it("Should update images in html, (api call made here to find s3 images file
|
|
236
|
+
it("Should update images in html, (api call made here to find s3 images file sizes which are not in window object)", async () => {
|
|
236
237
|
/**
|
|
237
238
|
* Out of 3 images. One s3 url is in window object, one is cdn url and another is also s3 url but not in window object.
|
|
238
239
|
*/
|
|
239
240
|
cdnTransformationHandler.server.listen();
|
|
240
241
|
cdnTransformationHandler.server.use(cdnTransformationHandler.getS3FileSizesFromUrlSuccessResponse2);
|
|
241
|
-
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
242
|
+
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
|
|
242
243
|
localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
|
|
243
244
|
window[cdnUtils.CREATIVES_S3_ASSET_FILESIZES] = {
|
|
244
245
|
"https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/394c053f-309b-49fc-ba74-6e700b1.jpeg": 76800, //this image has quality 85 since file size less than 80kb
|