@capillarytech/creatives-library 8.0.128 → 8.0.129
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/constants.js +1 -0
- package/package.json +1 -1
- package/services/api.js +1 -1
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -3
- package/tests/integration/TemplateCreation/api-response.js +5 -0
- package/tests/integration/TemplateCreation/msw-handler.js +42 -63
- package/utils/common.js +7 -0
- package/utils/commonUtils.js +2 -6
- package/utils/createPayload.js +405 -0
- package/utils/tests/createPayload.test.js +785 -0
- package/v2Components/CapImageUpload/index.js +59 -46
- package/v2Components/CapInAppCTA/index.js +1 -0
- package/v2Components/CapMpushCTA/constants.js +25 -0
- package/v2Components/CapMpushCTA/index.js +402 -0
- package/v2Components/CapMpushCTA/index.scss +95 -0
- package/v2Components/CapMpushCTA/messages.js +101 -0
- package/v2Components/CapTagList/index.js +177 -120
- package/v2Components/CapVideoUpload/constants.js +3 -0
- package/v2Components/CapVideoUpload/index.js +167 -110
- package/v2Components/CapVideoUpload/messages.js +16 -0
- package/v2Components/Carousel/index.js +15 -13
- package/v2Components/ErrorInfoNote/style.scss +1 -0
- package/v2Components/MobilePushPreviewV2/index.js +37 -5
- package/v2Components/TemplatePreview/_templatePreview.scss +114 -72
- package/v2Components/TemplatePreview/assets/images/Android _ With date and time.svg +29 -0
- package/v2Components/TemplatePreview/assets/images/android.svg +9 -0
- package/v2Components/TemplatePreview/assets/images/iOS _ With date and time.svg +26 -0
- package/v2Components/TemplatePreview/assets/images/ios.svg +9 -0
- package/v2Components/TemplatePreview/index.js +178 -50
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -62
- package/v2Containers/CreativesContainer/index.js +191 -136
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -22
- package/v2Containers/InApp/constants.js +1 -0
- package/v2Containers/InApp/index.js +13 -13
- package/v2Containers/MobilePush/Create/index.js +1 -0
- package/v2Containers/MobilePush/commonMethods.js +7 -14
- package/v2Containers/MobilePushNew/actions.js +116 -0
- package/v2Containers/MobilePushNew/components/CtaButtons.js +181 -0
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +834 -0
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +345 -0
- package/v2Containers/MobilePushNew/components/index.js +5 -0
- package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +798 -0
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +2114 -0
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +343 -0
- package/v2Containers/MobilePushNew/constants.js +115 -0
- package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +1299 -0
- package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1223 -0
- package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +246 -0
- package/v2Containers/MobilePushNew/hooks/useUpload.js +726 -0
- package/v2Containers/MobilePushNew/index.js +3412 -0
- package/v2Containers/MobilePushNew/index.scss +308 -0
- package/v2Containers/MobilePushNew/messages.js +242 -0
- package/v2Containers/MobilePushNew/reducer.js +160 -0
- package/v2Containers/MobilePushNew/sagas.js +198 -0
- package/v2Containers/MobilePushNew/selectors.js +55 -0
- package/v2Containers/MobilePushNew/tests/reducer.test.js +741 -0
- package/v2Containers/MobilePushNew/tests/sagas.test.js +863 -0
- package/v2Containers/MobilePushNew/tests/selectors.test.js +425 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +322 -0
- package/v2Containers/MobilePushNew/utils.js +33 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +5 -5
- package/v2Containers/TagList/index.js +56 -10
- package/v2Containers/Templates/_templates.scss +101 -1
- package/v2Containers/Templates/index.js +147 -35
- package/v2Containers/Templates/messages.js +8 -0
- package/v2Containers/Templates/sagas.js +2 -0
- package/v2Containers/Whatsapp/constants.js +1 -0
|
@@ -18,6 +18,7 @@ export const JP_LOCALE_HIDE_FEATURE = 'JP_LOCALE_HIDE_FEATURE';
|
|
|
18
18
|
export const ENABLE_WECHAT = 'ENABLE_WECHAT';
|
|
19
19
|
export const ENABLE_CUSTOMER_BARCODE_TAG = "ENABLE_CUSTOMER_BARCODE_TAG";
|
|
20
20
|
export const EMAIL_UNSUBSCRIBE_TAG_MANDATORY = "EMAIL_UNSUBSCRIBE_TAG_MANDATORY";
|
|
21
|
+
export const ENABLE_NEW_MPUSH = "ENABLE_NEW_MPUSH";
|
|
21
22
|
|
|
22
23
|
export const CARD_RELATED_TAGS = [
|
|
23
24
|
'card_series',
|
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -277,7 +277,7 @@ export const createWeChatTemplate = ({template}) => {
|
|
|
277
277
|
return request(url, getAPICallObject('POST', template));
|
|
278
278
|
};
|
|
279
279
|
|
|
280
|
-
export const createMobilePushTemplate = (
|
|
280
|
+
export const createMobilePushTemplate = (template) => {
|
|
281
281
|
const url = `${API_ENDPOINT}/templates/MOBILEPUSH`;
|
|
282
282
|
return request(url, getAPICallObject('POST', template));
|
|
283
283
|
};
|
|
@@ -32,7 +32,7 @@ jest.mock('@capillarytech/cap-ui-utils', () => ({
|
|
|
32
32
|
},
|
|
33
33
|
}));
|
|
34
34
|
|
|
35
|
-
jest.setTimeout(
|
|
35
|
+
jest.setTimeout(60000);
|
|
36
36
|
|
|
37
37
|
const initializeCreatives = () => {
|
|
38
38
|
const store = configureStore(mockInitialState, initialReducer, history);
|
|
@@ -51,6 +51,12 @@ beforeEach(() => {
|
|
|
51
51
|
server.listen();
|
|
52
52
|
localStorage.clear();
|
|
53
53
|
localStorage.setItem('token', true);
|
|
54
|
+
|
|
55
|
+
// Mock cap auth
|
|
56
|
+
window.capAuth = {
|
|
57
|
+
hasFeatureAccess: jest.fn().mockReturnValue(false),
|
|
58
|
+
isInitialized: jest.fn().mockReturnValue(true),
|
|
59
|
+
};
|
|
54
60
|
});
|
|
55
61
|
|
|
56
62
|
// clean up once the tests are done
|
|
@@ -62,7 +68,6 @@ afterAll(() => {
|
|
|
62
68
|
});
|
|
63
69
|
|
|
64
70
|
describe("Creatives testing template creation", () => {
|
|
65
|
-
jest.useFakeTimers();
|
|
66
71
|
it("Should navigate from creatives home page and create template in RCS channel", async () => {
|
|
67
72
|
initializeCreatives();
|
|
68
73
|
const creativesScreen = await helper.getCreativesScreen();
|
|
@@ -93,7 +98,7 @@ describe("Creatives testing template creation", () => {
|
|
|
93
98
|
name: globalMessages.email.defaultMessage,
|
|
94
99
|
});
|
|
95
100
|
await userEvent.click(whatsapp);
|
|
96
|
-
waitFor(() => {
|
|
101
|
+
await waitFor(() => {
|
|
97
102
|
expect(
|
|
98
103
|
creativesScreen.getByText(
|
|
99
104
|
/Whatsapp accounts are not setup for your brand/i
|
|
@@ -20418,3 +20418,8 @@ export const tag = {
|
|
|
20418
20418
|
}
|
|
20419
20419
|
};
|
|
20420
20420
|
|
|
20421
|
+
export const cdnTransformationConfig = {
|
|
20422
|
+
enabled: false,
|
|
20423
|
+
transformations: [],
|
|
20424
|
+
};
|
|
20425
|
+
|
|
@@ -4,73 +4,52 @@ import 'whatwg-fetch';
|
|
|
4
4
|
import * as apiResponse from './api-response';
|
|
5
5
|
|
|
6
6
|
import config from '../../../config/app';
|
|
7
|
+
import { MOBILE_PUSH } from '../../../v2Containers/CreativesContainer/constants';
|
|
7
8
|
const API_ENDPOINT = config.development.api_endpoint;
|
|
8
9
|
const AUTH_ENDPOINT = config.development.auth_endpoint;
|
|
9
10
|
const CAMPAIGNS_API_ENDPOINT = config.development.campaigns_api_org_endpoint;
|
|
10
11
|
|
|
11
12
|
export const server = setupServer(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
}),
|
|
13
|
+
rest.options('*', (req, res, ctx) => res(
|
|
14
|
+
ctx.status(200),
|
|
15
|
+
ctx.set('access-control-allow-origin', '*'),
|
|
16
|
+
ctx.set('Access-Control-Allow-Headers', 'x-cap-org'),
|
|
17
|
+
ctx.set('Access-Control-Allow-Headers', 'x-cap-remote-user'),
|
|
18
|
+
ctx.set('Access-Control-Allow-Headers', 'x-cap-api-data-context-org-id'),
|
|
19
|
+
ctx.set('Access-Control-Allow-Headers', 'x-cap-api-auth-org-id'),
|
|
20
|
+
)),
|
|
21
|
+
rest.get(`${AUTH_ENDPOINT}/auth/org/:orgId/users`, (req, res, ctx) => {
|
|
22
|
+
const { orgId } = req.params;
|
|
23
|
+
switch (orgId) {
|
|
24
|
+
case '50146':
|
|
25
|
+
return res(ctx.status(200), ctx.json(apiResponse.authOrgUsersReonData));
|
|
26
|
+
}
|
|
27
|
+
}),
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
res(ctx.status(200), ctx.json(apiResponse.authUserReonData)),
|
|
32
|
-
),
|
|
29
|
+
rest.get(`${AUTH_ENDPOINT}/user`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.authUserReonData))),
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
res(ctx.status(200), ctx.json(apiResponse.gallery)),
|
|
60
|
-
),
|
|
61
|
-
rest.get(`${API_ENDPOINT}/templates/v1/Line`, (req, res, ctx) =>
|
|
62
|
-
res(ctx.status(200), ctx.json(apiResponse.lineTemplates)),
|
|
63
|
-
),
|
|
64
|
-
rest.get(`${API_ENDPOINT}/templates/v1/Viber`, (req, res, ctx) =>
|
|
65
|
-
res(ctx.status(200), ctx.json(apiResponse.viberTemplates)),
|
|
66
|
-
),
|
|
67
|
-
rest.get(`${API_ENDPOINT}/templates/v1/Email`, (req, res, ctx) =>
|
|
68
|
-
res(ctx.status(200), ctx.json(apiResponse.emailTemplates)),
|
|
69
|
-
),
|
|
70
|
-
rest.get(`${API_ENDPOINT}/meta/TAG`, (req, res, ctx) =>
|
|
71
|
-
res(ctx.status(200), ctx.json(apiResponse.tag)),
|
|
72
|
-
),
|
|
73
|
-
rest.get(`${CAMPAIGNS_API_ENDPOINT}/meta/domainProperties`, (req, res, ctx) =>
|
|
74
|
-
res(ctx.status(200), ctx.json(apiResponse.domainProperties)),
|
|
75
|
-
),
|
|
76
|
-
);
|
|
31
|
+
rest.get(`${AUTH_ENDPOINT}/org/users`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.authOrgUsersReonData))),
|
|
32
|
+
rest.get(`${API_ENDPOINT}/templates/Sms`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.smsTemplates))),
|
|
33
|
+
rest.get(`${API_ENDPOINT}/templates/v1/Sms`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.smsTemplates))),
|
|
34
|
+
rest.get(`${API_ENDPOINT}/templates/Rcs`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.rcsTemplates))),
|
|
35
|
+
rest.get(`${API_ENDPOINT}/templates/v1/Rcs`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.rcsTemplates))),
|
|
36
|
+
rest.get(`${API_ENDPOINT}/meta/wecrm`, (req, res, ctx) => {
|
|
37
|
+
const sourceName = req.url.searchParams.get('source_name');
|
|
38
|
+
switch (sourceName) {
|
|
39
|
+
case 'WHATSAPP':
|
|
40
|
+
return res(ctx.status(200), ctx.json(apiResponse.whatsappAccount));
|
|
41
|
+
case 'VIBER':
|
|
42
|
+
return res(ctx.status(200), ctx.json(apiResponse.viberAccount));
|
|
43
|
+
case MOBILE_PUSH:
|
|
44
|
+
return res(ctx.status(200), ctx.json(apiResponse.mpushAccount));
|
|
45
|
+
default:
|
|
46
|
+
return res(ctx.status(200), ctx.json(apiResponse.whatsappAccount));
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
rest.get(`${API_ENDPOINT}/assets/image`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.gallery))),
|
|
50
|
+
rest.get(`${API_ENDPOINT}/templates/v1/Line`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.lineTemplates))),
|
|
51
|
+
rest.get(`${API_ENDPOINT}/templates/v1/Viber`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.viberTemplates))),
|
|
52
|
+
rest.get(`${API_ENDPOINT}/templates/v1/Email`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.emailTemplates))),
|
|
53
|
+
rest.get(`${API_ENDPOINT}/meta/TAG`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.tag))),
|
|
54
|
+
rest.get(`${CAMPAIGNS_API_ENDPOINT}/meta/domainProperties`, (req, res, ctx) => res(ctx.status(200), ctx.json(apiResponse.domainProperties))),
|
|
55
|
+
);
|
package/utils/common.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
BADGES_ISSUE,
|
|
23
23
|
ENABLE_WECHAT,
|
|
24
24
|
LIQUID_SUPPORT,
|
|
25
|
+
ENABLE_NEW_MPUSH,
|
|
25
26
|
} from '../containers/App/constants';
|
|
26
27
|
import { apiMessageFormatHandler } from './commonUtils';
|
|
27
28
|
|
|
@@ -124,6 +125,12 @@ export const isEmailUnsubscribeTagMandatory = Auth.hasFeatureAccess.bind(
|
|
|
124
125
|
null,
|
|
125
126
|
EMAIL_UNSUBSCRIBE_TAG_MANDATORY,
|
|
126
127
|
);
|
|
128
|
+
|
|
129
|
+
export const hasNewMobilePushFeatureEnabled = Auth.hasFeatureAccess.bind(
|
|
130
|
+
null,
|
|
131
|
+
ENABLE_NEW_MPUSH,
|
|
132
|
+
);
|
|
133
|
+
|
|
127
134
|
//filtering tags based on scope
|
|
128
135
|
export const filterTags = (tagsToFilter, tagsList) => tagsList?.filter(
|
|
129
136
|
(tag) => !tagsToFilter?.includes(tag?.definition?.value)
|
package/utils/commonUtils.js
CHANGED
|
@@ -35,7 +35,7 @@ export const addBaseToTemplate = (template) => {
|
|
|
35
35
|
...template.versions,
|
|
36
36
|
base: {
|
|
37
37
|
...history[0],
|
|
38
|
-
...( get(template, 'versions.base.subject') ? {subject
|
|
38
|
+
...( get(template, 'versions.base.subject') ? {subject: get(template, 'versions.base.subject')} : { subject: history?.[0]?.subject }),
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
41
|
});
|
|
@@ -151,7 +151,7 @@ export const validateLiquidTemplateContent = async (
|
|
|
151
151
|
const emptyBodyError = formatMessage(messages?.emailBodyEmptyError);
|
|
152
152
|
const somethingWrongMsg = formatMessage(messages?.somethingWentWrong);
|
|
153
153
|
// Empty content check
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
if (!content || content.trim() === "") {
|
|
156
156
|
onError({
|
|
157
157
|
standardErrors: [emptyBodyError],
|
|
@@ -170,7 +170,6 @@ export const validateLiquidTemplateContent = async (
|
|
|
170
170
|
|
|
171
171
|
const {askAiraResponse: result, isError} = await getLiquidTagsAsync(content);
|
|
172
172
|
const validString = /\S/.test(content);
|
|
173
|
-
|
|
174
173
|
// Handle API errors or empty content
|
|
175
174
|
if (result?.errors?.length > 0 || !validString || isError) {
|
|
176
175
|
let standardErrors = [];
|
|
@@ -302,7 +301,6 @@ export const _validatePlatformSpecificContent = async (
|
|
|
302
301
|
if (singleTab === IOS) {
|
|
303
302
|
isAndroidValid = true;
|
|
304
303
|
}
|
|
305
|
-
|
|
306
304
|
return isAndroidValid && isIosValid; // Overall success
|
|
307
305
|
};
|
|
308
306
|
|
|
@@ -340,7 +338,6 @@ export const validateMobilePushContent = async (formData, options) => {
|
|
|
340
338
|
if (iosContent) return [iosContent, IOS?.toLowerCase()];
|
|
341
339
|
return ["", ""];
|
|
342
340
|
};
|
|
343
|
-
|
|
344
341
|
if (overallSuccess) {
|
|
345
342
|
const [contentToSubmit, tabTypeToSubmit] = getContentToSubmit();
|
|
346
343
|
// Call the FINAL onSuccess only ONCE here
|
|
@@ -373,7 +370,6 @@ export const validateInAppContent = async (formData, options) => {
|
|
|
373
370
|
onSuccess, // FINAL success callback
|
|
374
371
|
...restOptions // Options for validateLiquidTemplateContent
|
|
375
372
|
} = options;
|
|
376
|
-
|
|
377
373
|
// Clear previous errors with new structure
|
|
378
374
|
onError({
|
|
379
375
|
standardErrors: {
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
// Utility to create the Mobile Push payload for rich media, with validation
|
|
2
|
+
import { DEEP_LINK } from "../v2Components/CapInAppCTA/constants";
|
|
3
|
+
import { EXTERNAL_URL } from "../v2Components/CapMpushCTA/constants";
|
|
4
|
+
import {
|
|
5
|
+
IMAGE, VIDEO, GIF, CAROUSEL, TEXT, BIG_TEXT, BIG_PICTURE, MOBILE_PUSH_CHANNEL,
|
|
6
|
+
EXTERNAL_LINK,
|
|
7
|
+
ANDROID,
|
|
8
|
+
IOS,
|
|
9
|
+
MANUAL_CAROUSEL,
|
|
10
|
+
AUTO_CAROUSEL,
|
|
11
|
+
FILMSTRIP_CAROUSEL,
|
|
12
|
+
} from "../v2Containers/MobilePushNew/constants";
|
|
13
|
+
import { NONE } from "../v2Containers/Whatsapp/constants";
|
|
14
|
+
/**
|
|
15
|
+
* Constructs the payload for Mobile Push, supporting both legacy and rich media as per backend contract.
|
|
16
|
+
* - Trims and validates templateName
|
|
17
|
+
* - Handles legacy and rich media (VIDEO, GIF, CAROUSEL, etc.)
|
|
18
|
+
* - Builds androidContent and iosContent with correct structure
|
|
19
|
+
* @param {Object} params - All required params
|
|
20
|
+
* @param {string} params.templateName
|
|
21
|
+
* @param {Object} params.androidContent
|
|
22
|
+
* @param {Object} params.iosContent
|
|
23
|
+
* @param {Object} params.accountData
|
|
24
|
+
* @param {Object} [params.options] - Optional extra options (ouId, actionId, etc.)
|
|
25
|
+
* @returns {Object} - Payload ready for backend
|
|
26
|
+
*/
|
|
27
|
+
function createPayload({
|
|
28
|
+
templateName, androidContent, iosContent, imageSrc = {}, mpushVideoSrcAndPreview = {}, accountData, options = {}, sameContent = false,
|
|
29
|
+
} = {}) {
|
|
30
|
+
// Platform support checks must be at the top
|
|
31
|
+
const isAndroidSupported = accountData?.configs?.android === '1';
|
|
32
|
+
const isIosSupported = accountData?.configs?.ios === '1';
|
|
33
|
+
// Validate and trim template name
|
|
34
|
+
const trimmedTemplateName = (templateName || "").trim();
|
|
35
|
+
if (!trimmedTemplateName) {
|
|
36
|
+
throw new Error("Template name is empty or contains only white spaces");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate androidContent and iosContent
|
|
40
|
+
if (androidContent && (!androidContent?.title || !androidContent?.message)) {
|
|
41
|
+
throw new Error("Android content must have title and message");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (iosContent && (!iosContent?.title || !iosContent?.message)) {
|
|
45
|
+
throw new Error("iOS content must have title and message");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate accountData
|
|
49
|
+
if (!accountData) {
|
|
50
|
+
throw new Error("Account data is required. Please select a MobilePush account.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const accountId = accountData?.accountId || accountData?.id;
|
|
54
|
+
const licenseCode = accountData?.licenseCode || accountData?.sourceAccountIdentifier;
|
|
55
|
+
const sourceType = accountData?.sourceType || accountData?.sourceTypeName;
|
|
56
|
+
|
|
57
|
+
// Ensure imageSrc has the required properties
|
|
58
|
+
const safeImageSrc = {
|
|
59
|
+
androidImageSrc: imageSrc?.androidImageSrc || "",
|
|
60
|
+
iosImageSrc: imageSrc?.iosImageSrc || "",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Helper to build expandableDetails for legacy and rich media
|
|
64
|
+
function buildExpandableDetails(content, contentImageSrc, platform = ANDROID) {
|
|
65
|
+
const style = content?.expandableDetails?.style || content?.mediaType || BIG_TEXT;
|
|
66
|
+
const expandableDetails = { style };
|
|
67
|
+
|
|
68
|
+
if (style === IMAGE || style === BIG_PICTURE) {
|
|
69
|
+
expandableDetails.style = BIG_PICTURE;
|
|
70
|
+
expandableDetails.message = content?.message;
|
|
71
|
+
expandableDetails.image = contentImageSrc || "";
|
|
72
|
+
} else if (style === VIDEO) {
|
|
73
|
+
expandableDetails.message = content?.message;
|
|
74
|
+
// Always build media array for VIDEO
|
|
75
|
+
let url = '';
|
|
76
|
+
let text = '';
|
|
77
|
+
let videoPreviewUrl = '';
|
|
78
|
+
if (mpushVideoSrcAndPreview?.mpushVideoSrc) {
|
|
79
|
+
url = mpushVideoSrcAndPreview?.mpushVideoSrc;
|
|
80
|
+
videoPreviewUrl = mpushVideoSrcAndPreview?.mpushVideoPreviewImg || '';
|
|
81
|
+
} else if (content?.mediaList && content?.mediaList[0]) {
|
|
82
|
+
const { url: mediaUrl = '', text: mediaText = '' } = content?.mediaList[0];
|
|
83
|
+
url = mediaUrl;
|
|
84
|
+
text = mediaText;
|
|
85
|
+
}
|
|
86
|
+
expandableDetails.media = [
|
|
87
|
+
{
|
|
88
|
+
url,
|
|
89
|
+
text,
|
|
90
|
+
type: VIDEO,
|
|
91
|
+
videoPreviewUrl,
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
} else if (style === GIF) {
|
|
95
|
+
expandableDetails.message = content?.message;
|
|
96
|
+
// Handle GIF: preserve original type from mediaList if available, otherwise convert to VIDEO
|
|
97
|
+
let url = '';
|
|
98
|
+
let text = '';
|
|
99
|
+
let mediaType = VIDEO; // Default to VIDEO for backend compatibility
|
|
100
|
+
|
|
101
|
+
if (content?.mediaList && content?.mediaList[0]) {
|
|
102
|
+
// If mediaList exists, use it and preserve the original type
|
|
103
|
+
const { url: mediaUrl = '', text: mediaText = '', type: originalType = VIDEO } = content?.mediaList[0];
|
|
104
|
+
url = mediaUrl;
|
|
105
|
+
text = mediaText;
|
|
106
|
+
mediaType = originalType; // Preserve original type from mediaList
|
|
107
|
+
} else if (mpushVideoSrcAndPreview?.mpushVideoSrc) {
|
|
108
|
+
// If no mediaList but mpushVideoSrc exists, convert to VIDEO
|
|
109
|
+
url = mpushVideoSrcAndPreview?.mpushVideoSrc;
|
|
110
|
+
mediaType = VIDEO;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
expandableDetails.media = [
|
|
114
|
+
{
|
|
115
|
+
url,
|
|
116
|
+
text,
|
|
117
|
+
type: mediaType,
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
} else if (style === BIG_TEXT || style === TEXT || style === NONE) {
|
|
121
|
+
expandableDetails.style = BIG_TEXT;
|
|
122
|
+
expandableDetails.message = content?.message;
|
|
123
|
+
} else if ((style === CAROUSEL || style === MANUAL_CAROUSEL || style === AUTO_CAROUSEL || style === FILMSTRIP_CAROUSEL)) {
|
|
124
|
+
// Handle carousel data
|
|
125
|
+
expandableDetails.style = 'MANUAL_CAROUSEL';
|
|
126
|
+
expandableDetails.message = content?.message;
|
|
127
|
+
|
|
128
|
+
// Add carousel data if it exists
|
|
129
|
+
if (content?.carouselData && Array.isArray(content?.carouselData) && content?.carouselData.length > 0) {
|
|
130
|
+
expandableDetails.carouselData = content?.carouselData.map((card) => ({
|
|
131
|
+
mediaType: card.mediaType || 'image',
|
|
132
|
+
imageUrl: card.imageUrl || '',
|
|
133
|
+
videoSrc: card.videoSrc || '',
|
|
134
|
+
buttons: card.buttons ? card.buttons.map((button) => {
|
|
135
|
+
if (button.linkType === DEEP_LINK && button.deepLinkKeys && button.deepLinkValue) {
|
|
136
|
+
const deepLinkKeys = Array.isArray(button.deepLinkKeys) ? button.deepLinkKeys : [button.deepLinkKeys];
|
|
137
|
+
const updatedDeepLinkValue = appendDeepLinkKeysToUrl(button.deepLinkValue, deepLinkKeys);
|
|
138
|
+
return {
|
|
139
|
+
actionOnClick: button.actionOnClick,
|
|
140
|
+
linkType: button.linkType,
|
|
141
|
+
deepLinkValue: updatedDeepLinkValue,
|
|
142
|
+
externalLinkValue: button.externalLinkValue,
|
|
143
|
+
// deepLinkKeys property removed - not expected by backend
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
actionOnClick: button.actionOnClick,
|
|
148
|
+
linkType: button.linkType,
|
|
149
|
+
deepLinkValue: button.deepLinkValue,
|
|
150
|
+
externalLinkValue: button.externalLinkValue,
|
|
151
|
+
// deepLinkKeys property removed - not expected by backend
|
|
152
|
+
};
|
|
153
|
+
}) : [],
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle mediaList for carousel (used in tests and some scenarios)
|
|
158
|
+
if (content?.mediaList && Array.isArray(content?.mediaList) && content?.mediaList.length > 0) {
|
|
159
|
+
expandableDetails.media = content?.mediaList.map((m) => ({
|
|
160
|
+
url: m.url,
|
|
161
|
+
text: m.text || "",
|
|
162
|
+
type: m.type,
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
} else if ([GIF].includes(style) && content?.mediaList) {
|
|
166
|
+
expandableDetails.media = (content?.mediaList || []).map((m) => ({
|
|
167
|
+
url: m.url,
|
|
168
|
+
text: m.text || "",
|
|
169
|
+
type: m.type,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// iOS always needs ctas array
|
|
174
|
+
if (platform === IOS) {
|
|
175
|
+
expandableDetails.ctas = content?.expandableDetails?.ctas || [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Process button CTAs to append deep link keys as query parameters
|
|
179
|
+
if (content?.expandableDetails?.ctas && Array.isArray(content.expandableDetails.ctas)) {
|
|
180
|
+
expandableDetails.ctas = content.expandableDetails.ctas.map((cta) => {
|
|
181
|
+
if (cta.type === DEEP_LINK && cta.deepLinkKeys && cta.actionLink) {
|
|
182
|
+
const deepLinkKeys = Array.isArray(cta.deepLinkKeys) ? cta.deepLinkKeys : [cta.deepLinkKeys];
|
|
183
|
+
const updatedActionLink = appendDeepLinkKeysToUrl(cta.actionLink, deepLinkKeys);
|
|
184
|
+
return {
|
|
185
|
+
actionText: cta.actionText,
|
|
186
|
+
type: cta.type,
|
|
187
|
+
actionLink: updatedActionLink,
|
|
188
|
+
// deepLinkKeys property removed - not expected by backend
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
actionText: cta.actionText,
|
|
193
|
+
type: cta.type,
|
|
194
|
+
actionLink: cta.actionLink,
|
|
195
|
+
// deepLinkKeys property removed - not expected by backend
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add any additional expandableDetails fields
|
|
201
|
+
if (content?.expandableDetails) {
|
|
202
|
+
Object.entries(content?.expandableDetails).forEach(([k, v]) => {
|
|
203
|
+
if (!(k in expandableDetails)) expandableDetails[k] = v;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return expandableDetails;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Helper to build custom fields array (legacy)
|
|
210
|
+
// Helper function to append deep link keys as query parameters
|
|
211
|
+
function appendDeepLinkKeysToUrl(baseUrl, deepLinkKeys) {
|
|
212
|
+
if (!baseUrl || !deepLinkKeys || deepLinkKeys.length === 0) {
|
|
213
|
+
return baseUrl;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const url = new URL(baseUrl);
|
|
218
|
+
deepLinkKeys.forEach((key) => {
|
|
219
|
+
if (key) {
|
|
220
|
+
url.searchParams.set(key, key);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return url.toString();
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// If URL parsing fails, append as query string manually
|
|
226
|
+
const separator = baseUrl.includes('?') ? '&' : '?';
|
|
227
|
+
const queryParams = deepLinkKeys
|
|
228
|
+
.filter((key) => key)
|
|
229
|
+
.map((key) => `${key}=${key}`)
|
|
230
|
+
.join('&');
|
|
231
|
+
return queryParams ? `${baseUrl}${separator}${queryParams}` : baseUrl;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function buildCustomFields(content) {
|
|
236
|
+
let customFields = [];
|
|
237
|
+
|
|
238
|
+
// If content.custom is already an array, use it as base
|
|
239
|
+
if (Array.isArray(content?.custom)) {
|
|
240
|
+
customFields = [...content.custom];
|
|
241
|
+
}
|
|
242
|
+
// If it's an object, map to array
|
|
243
|
+
else if (content?.custom && typeof content?.custom === 'object') {
|
|
244
|
+
customFields = Object.entries(content?.custom).map(([key, value]) => ({ key, value }));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add deepLinkKeys to custom fields if it exists
|
|
248
|
+
if (content?.deepLinkKeysValue && content?.linkType === DEEP_LINK) {
|
|
249
|
+
if (Array.isArray(content.deepLinkKeysValue)) {
|
|
250
|
+
// Handle array of deep link keys
|
|
251
|
+
content.deepLinkKeysValue.forEach((key) => {
|
|
252
|
+
if (key) {
|
|
253
|
+
customFields.push({
|
|
254
|
+
key,
|
|
255
|
+
value: key,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
} else if (typeof content.deepLinkKeysValue === 'string' && content.deepLinkKeysValue) {
|
|
260
|
+
// Handle single string value (backward compatibility)
|
|
261
|
+
customFields.push({
|
|
262
|
+
key: content.deepLinkKeysValue,
|
|
263
|
+
value: content.deepLinkKeysValue,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Add carousel button deep link keys to custom fields
|
|
269
|
+
if (content?.carouselData && Array.isArray(content.carouselData)) {
|
|
270
|
+
content.carouselData.forEach((card) => {
|
|
271
|
+
if (card.buttons && Array.isArray(card.buttons)) {
|
|
272
|
+
card.buttons.forEach((button) => {
|
|
273
|
+
if (button.linkType === DEEP_LINK && button.deepLinkKeys) {
|
|
274
|
+
if (Array.isArray(button.deepLinkKeys)) {
|
|
275
|
+
// Handle array of deep link keys
|
|
276
|
+
button.deepLinkKeys.forEach((key) => {
|
|
277
|
+
if (key) {
|
|
278
|
+
customFields.push({
|
|
279
|
+
key,
|
|
280
|
+
value: key,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
} else if (typeof button.deepLinkKeys === 'string' && button.deepLinkKeys) {
|
|
285
|
+
// Handle single string value (backward compatibility)
|
|
286
|
+
customFields.push({
|
|
287
|
+
key: button.deepLinkKeys,
|
|
288
|
+
value: button.deepLinkKeys,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return customFields;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Compose androidContent for legacy payload
|
|
301
|
+
let androidLegacy;
|
|
302
|
+
if (isAndroidSupported && isValidContent(androidContent)) {
|
|
303
|
+
androidLegacy = {
|
|
304
|
+
luid: "{{luid}}",
|
|
305
|
+
cuid: "{{cuid}}",
|
|
306
|
+
communicationId: "{{communicationId}}",
|
|
307
|
+
title: androidContent.title,
|
|
308
|
+
message: androidContent.message,
|
|
309
|
+
expandableDetails: buildExpandableDetails(androidContent, safeImageSrc.androidImageSrc),
|
|
310
|
+
custom: buildCustomFields(androidContent),
|
|
311
|
+
type: androidContent.mediaType,
|
|
312
|
+
deviceType: 'ANDROID',
|
|
313
|
+
};
|
|
314
|
+
if (androidContent?.actionOnClick && (androidContent?.deepLinkValue || androidContent?.externalLinkValue)) {
|
|
315
|
+
let actionLink = androidContent?.linkType === EXTERNAL_LINK ? androidContent?.externalLinkValue : androidContent?.deepLinkValue;
|
|
316
|
+
|
|
317
|
+
// Append deep link keys as query parameters for deep links
|
|
318
|
+
if (androidContent?.linkType === DEEP_LINK && androidContent?.deepLinkKeysValue && androidContent.deepLinkKeysValue.length > 0) {
|
|
319
|
+
actionLink = appendDeepLinkKeysToUrl(actionLink, androidContent.deepLinkKeysValue);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
androidLegacy.cta = {
|
|
323
|
+
type: androidContent.linkType === EXTERNAL_LINK ? EXTERNAL_URL : DEEP_LINK,
|
|
324
|
+
actionLink,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Compose iosContent for legacy payload
|
|
330
|
+
let iosLegacy;
|
|
331
|
+
if (isIosSupported && isValidContent(iosContent)) {
|
|
332
|
+
iosLegacy = {
|
|
333
|
+
luid: "{{luid}}",
|
|
334
|
+
cuid: "{{cuid}}",
|
|
335
|
+
communicationId: "{{communicationId}}",
|
|
336
|
+
title: iosContent.title,
|
|
337
|
+
message: iosContent.message,
|
|
338
|
+
expandableDetails: buildExpandableDetails(iosContent, safeImageSrc.iosImageSrc, 'IOS'),
|
|
339
|
+
custom: buildCustomFields(iosContent),
|
|
340
|
+
type: iosContent.mediaType,
|
|
341
|
+
deviceType: 'IOS',
|
|
342
|
+
};
|
|
343
|
+
if (iosContent?.actionOnClick && (iosContent?.deepLinkValue || iosContent?.externalLinkValue)) {
|
|
344
|
+
let actionLink = iosContent?.linkType === EXTERNAL_LINK ? iosContent?.externalLinkValue : iosContent?.deepLinkValue;
|
|
345
|
+
|
|
346
|
+
// Append deep link keys as query parameters for deep links
|
|
347
|
+
if (iosContent?.linkType === DEEP_LINK && iosContent?.deepLinkKeysValue && iosContent.deepLinkKeysValue.length > 0) {
|
|
348
|
+
actionLink = appendDeepLinkKeysToUrl(actionLink, iosContent.deepLinkKeysValue);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
iosLegacy.cta = {
|
|
352
|
+
type: iosContent?.linkType === EXTERNAL_LINK ? EXTERNAL_URL : DEEP_LINK,
|
|
353
|
+
actionLink,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Helper to check if content is non-empty and valid
|
|
359
|
+
function isValidContent(content) {
|
|
360
|
+
return content && typeof content === 'object' && content.title && content.message;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Determine platform support from accountData
|
|
364
|
+
// const isAndroidSupported = accountData?.configs?.android === '1';
|
|
365
|
+
// const isIosSupported = accountData?.configs?.ios === '1';
|
|
366
|
+
|
|
367
|
+
if (isAndroidSupported && (!androidContent || !androidContent?.title || !androidContent?.message)) {
|
|
368
|
+
console.error('Android content missing or invalid:', androidContent);
|
|
369
|
+
throw new Error("Android content must have title and message");
|
|
370
|
+
}
|
|
371
|
+
if (isIosSupported && (!iosContent || !iosContent?.title || !iosContent?.message)) {
|
|
372
|
+
console.error('iOS content missing or invalid:', iosContent);
|
|
373
|
+
throw new Error("iOS content must have title and message");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Compose the full payload in legacy format
|
|
377
|
+
const versionsBase = {};
|
|
378
|
+
if (androidLegacy) versionsBase.ANDROID = androidLegacy;
|
|
379
|
+
if (iosLegacy) versionsBase.IOS = iosLegacy;
|
|
380
|
+
|
|
381
|
+
// Fallback template name to platform title if not provided
|
|
382
|
+
let finalTemplateName = trimmedTemplateName;
|
|
383
|
+
if (!finalTemplateName) {
|
|
384
|
+
finalTemplateName = (androidContent && androidContent.title) || (iosContent && iosContent.title) || '';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const payload = {
|
|
388
|
+
versions: {
|
|
389
|
+
base: versionsBase,
|
|
390
|
+
},
|
|
391
|
+
type: MOBILE_PUSH_CHANNEL,
|
|
392
|
+
name: finalTemplateName,
|
|
393
|
+
definition: {
|
|
394
|
+
accountId,
|
|
395
|
+
licenseCode,
|
|
396
|
+
mode: options?.mode || TEXT.toLowerCase(),
|
|
397
|
+
sourceType,
|
|
398
|
+
sameContent,
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
return payload;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export { createPayload };
|