@capillarytech/creatives-library 8.0.129 → 8.0.131
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/Login/index.js +1 -2
- package/containers/Templates/constants.js +10 -1
- package/containers/Templates/index.js +45 -45
- package/package.json +1 -1
- package/services/api.js +14 -7
- package/services/tests/haptic-api.test.js +387 -0
- package/utils/createMobilePushPayload.js +322 -0
- package/utils/tests/{createPayload.test.js → createMobilePushPayload.test.js} +333 -64
- package/utils/tests/vendorDataTransformers.test.js +512 -0
- package/utils/vendorDataTransformers.js +108 -0
- package/v2Components/CapDeviceContent/index.js +1 -1
- package/v2Components/CapDocumentUpload/index.js +2 -2
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapMpushCTA/index.js +13 -12
- package/v2Components/CapTagList/index.js +5 -5
- package/v2Components/CapVideoUpload/index.js +17 -7
- package/v2Components/MobilePushPreviewV2/index.js +28 -15
- package/v2Components/TemplatePreview/_templatePreview.scss +131 -29
- package/v2Components/TemplatePreview/index.js +130 -131
- package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +10 -10
- package/v2Containers/CreativesContainer/index.js +6 -4
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +4748 -4658
- package/v2Containers/Login/index.js +1 -2
- package/v2Containers/MobilePush/tests/commonMethods.test.js +401 -0
- package/v2Containers/MobilePushNew/components/CtaButtons.js +18 -16
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +46 -45
- package/v2Containers/MobilePushNew/components/PlatformContentFields.js +12 -11
- package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +134 -367
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +1209 -143
- package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +314 -3
- package/v2Containers/MobilePushNew/constants.js +1 -0
- package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +163 -0
- package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1131 -895
- package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +172 -52
- package/v2Containers/MobilePushNew/hooks/useUpload.js +88 -74
- package/v2Containers/MobilePushNew/index.js +278 -1532
- package/v2Containers/MobilePushNew/messages.js +30 -0
- package/v2Containers/MobilePushNew/sagas.js +2 -7
- package/v2Containers/MobilePushNew/tests/sagas.test.js +41 -40
- package/v2Containers/MobilePushNew/tests/selectors.test.js +240 -0
- package/v2Containers/MobilePushNew/tests/utils.test.js +118 -19
- package/v2Containers/MobilePushNew/utils.js +53 -2
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1171 -971
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +684 -424
- package/v2Containers/Templates/_templates.scss +0 -1
- package/v2Containers/Templates/index.js +58 -29
- package/v2Containers/Templates/sagas.js +0 -1
- package/v2Containers/Whatsapp/constants.js +32 -0
- package/v2Containers/Whatsapp/index.js +104 -25
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +3992 -3677
- package/v2Containers/Whatsapp/tests/haptic.test.js +405 -0
- package/assets/loading_img.gif +0 -0
- package/utils/createPayload.js +0 -405
- /package/v2Components/TemplatePreview/assets/images/{Android _ With date and time.svg → Android_With_date_and_time.svg} +0 -0
- /package/v2Components/TemplatePreview/assets/images/{iOS _ With date and time.svg → iOS_With_date_and_time.svg} +0 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { uploadFile, createWhatsappTemplate } from '../api';
|
|
2
|
+
|
|
3
|
+
// Mock fetch
|
|
4
|
+
global.fetch = jest.fn();
|
|
5
|
+
|
|
6
|
+
describe('HAPTIC API Integration Tests', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
fetch.mockClear();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('uploadFile with HAPTIC parameters', () => {
|
|
13
|
+
it('should append HAPTIC-specific parameters to FormData', () => {
|
|
14
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
15
|
+
const whatsappParams = {
|
|
16
|
+
source: 'whatsapp',
|
|
17
|
+
wabaId: 'waba-123',
|
|
18
|
+
access_token: 'haptic-token',
|
|
19
|
+
phoneId: 'phone-456',
|
|
20
|
+
hostName: 'hapticwhatsappbulk',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Mock successful response
|
|
24
|
+
fetch.mockResolvedValueOnce({
|
|
25
|
+
status: 200,
|
|
26
|
+
json: () => Promise.resolve({ success: true }),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
uploadFile({
|
|
30
|
+
file: mockFile,
|
|
31
|
+
assetType: 'whatsapp',
|
|
32
|
+
whatsappParams,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Verify fetch was called
|
|
36
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
37
|
+
|
|
38
|
+
const fetchCall = fetch.mock.calls[0];
|
|
39
|
+
const url = fetchCall[0];
|
|
40
|
+
const options = fetchCall[1];
|
|
41
|
+
|
|
42
|
+
expect(url).toContain('/assets/whatsapp');
|
|
43
|
+
expect(options.method).toBe('POST');
|
|
44
|
+
|
|
45
|
+
// Verify FormData contains HAPTIC parameters
|
|
46
|
+
const formData = options.body;
|
|
47
|
+
expect(formData).toBeInstanceOf(FormData);
|
|
48
|
+
|
|
49
|
+
// Check that FormData contains the file
|
|
50
|
+
expect(formData.get('file')).not.toBeNull();
|
|
51
|
+
|
|
52
|
+
// Check that FormData contains HAPTIC parameters
|
|
53
|
+
expect(formData.get('source')).toBe('whatsapp');
|
|
54
|
+
expect(formData.get('wabaId')).toBe('waba-123');
|
|
55
|
+
expect(formData.get('accessToken')).toBe('haptic-token'); // Note: API converts access_token to accessToken
|
|
56
|
+
expect(formData.get('phoneId')).toBe('phone-456');
|
|
57
|
+
expect(formData.get('hostName')).toBe('hapticwhatsappbulk');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle missing HAPTIC parameters gracefully', () => {
|
|
61
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
62
|
+
const whatsappParams = {
|
|
63
|
+
source: 'whatsapp',
|
|
64
|
+
// Missing parameters
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
fetch.mockResolvedValueOnce({
|
|
68
|
+
status: 200,
|
|
69
|
+
json: () => Promise.resolve({ success: true }),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
uploadFile({
|
|
73
|
+
file: mockFile,
|
|
74
|
+
assetType: 'whatsapp',
|
|
75
|
+
whatsappParams,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const fetchCall = fetch.mock.calls[0];
|
|
79
|
+
const formData = fetchCall[1].body;
|
|
80
|
+
|
|
81
|
+
expect(formData.get('source')).toBe('whatsapp');
|
|
82
|
+
expect(formData.get('wabaId')).toBe('undefined');
|
|
83
|
+
expect(formData.get('accessToken')).toBe('undefined');
|
|
84
|
+
expect(formData.get('phoneId')).toBe('undefined');
|
|
85
|
+
expect(formData.get('hostName')).toBe('undefined');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should handle undefined whatsappParams', () => {
|
|
89
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
90
|
+
|
|
91
|
+
fetch.mockResolvedValueOnce({
|
|
92
|
+
status: 200,
|
|
93
|
+
json: () => Promise.resolve({ success: true }),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
uploadFile({
|
|
97
|
+
file: mockFile,
|
|
98
|
+
assetType: 'whatsapp',
|
|
99
|
+
// whatsappParams is undefined
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const fetchCall = fetch.mock.calls[0];
|
|
103
|
+
const formData = fetchCall[1].body;
|
|
104
|
+
|
|
105
|
+
expect(formData.get('file')).not.toBeNull();
|
|
106
|
+
// When whatsappParams is undefined, no whatsapp-specific fields are added
|
|
107
|
+
expect(formData.get('source')).toBeNull();
|
|
108
|
+
expect(formData.get('wabaId')).toBeNull();
|
|
109
|
+
expect(formData.get('accessToken')).toBeNull();
|
|
110
|
+
expect(formData.get('phoneId')).toBeNull();
|
|
111
|
+
expect(formData.get('hostName')).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle empty string parameters', () => {
|
|
115
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
116
|
+
const whatsappParams = {
|
|
117
|
+
source: 'whatsapp',
|
|
118
|
+
wabaId: '',
|
|
119
|
+
access_token: '',
|
|
120
|
+
phoneId: '',
|
|
121
|
+
hostName: '',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
fetch.mockResolvedValueOnce({
|
|
125
|
+
status: 200,
|
|
126
|
+
json: () => Promise.resolve({ success: true }),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
uploadFile({
|
|
130
|
+
file: mockFile,
|
|
131
|
+
assetType: 'whatsapp',
|
|
132
|
+
whatsappParams,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const fetchCall = fetch.mock.calls[0];
|
|
136
|
+
const formData = fetchCall[1].body;
|
|
137
|
+
|
|
138
|
+
expect(formData.get('wabaId')).toBe('');
|
|
139
|
+
expect(formData.get('accessToken')).toBe('');
|
|
140
|
+
expect(formData.get('phoneId')).toBe('');
|
|
141
|
+
expect(formData.get('hostName')).toBe('');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should handle null parameters', () => {
|
|
145
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
146
|
+
const whatsappParams = {
|
|
147
|
+
source: 'whatsapp',
|
|
148
|
+
wabaId: null,
|
|
149
|
+
access_token: null,
|
|
150
|
+
phoneId: null,
|
|
151
|
+
hostName: null,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
fetch.mockResolvedValueOnce({
|
|
155
|
+
status: 200,
|
|
156
|
+
json: () => Promise.resolve({ success: true }),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
uploadFile({
|
|
160
|
+
file: mockFile,
|
|
161
|
+
assetType: 'whatsapp',
|
|
162
|
+
whatsappParams,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const fetchCall = fetch.mock.calls[0];
|
|
166
|
+
const formData = fetchCall[1].body;
|
|
167
|
+
|
|
168
|
+
expect(formData.get('wabaId')).toBe('null');
|
|
169
|
+
expect(formData.get('accessToken')).toBe('null');
|
|
170
|
+
expect(formData.get('phoneId')).toBe('null');
|
|
171
|
+
expect(formData.get('hostName')).toBe('null');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('createWhatsappTemplate with HAPTIC payload', () => {
|
|
176
|
+
it('should create FormData with HAPTIC payload structure', () => {
|
|
177
|
+
const payload = {
|
|
178
|
+
name: 'HAPTIC Test Template',
|
|
179
|
+
category: 'MARKETING',
|
|
180
|
+
content: 'Hello {{1}}',
|
|
181
|
+
wabaId: 'waba-123',
|
|
182
|
+
access_token: 'haptic-token',
|
|
183
|
+
PhoneId: 'phone-456',
|
|
184
|
+
hostName: 'hapticwhatsappbulk',
|
|
185
|
+
hapticFileHandle: 'haptic-handle-123',
|
|
186
|
+
status: 'unsubmitted',
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
190
|
+
|
|
191
|
+
fetch.mockResolvedValueOnce({
|
|
192
|
+
status: 200,
|
|
193
|
+
json: () => Promise.resolve({ success: true }),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
createWhatsappTemplate({ payload, gupshupMediaFile: mockFile });
|
|
197
|
+
|
|
198
|
+
const fetchCall = fetch.mock.calls[0];
|
|
199
|
+
const url = fetchCall[0];
|
|
200
|
+
const options = fetchCall[1];
|
|
201
|
+
|
|
202
|
+
expect(url).toContain('/templates/WHATSAPP');
|
|
203
|
+
expect(options.method).toBe('POST');
|
|
204
|
+
|
|
205
|
+
const formData = options.body;
|
|
206
|
+
expect(formData).toBeInstanceOf(FormData);
|
|
207
|
+
|
|
208
|
+
// Check payload JSON
|
|
209
|
+
const payloadJson = JSON.parse(formData.get('payload'));
|
|
210
|
+
expect(payloadJson).toEqual(payload);
|
|
211
|
+
|
|
212
|
+
// Check file
|
|
213
|
+
expect(formData.get('gupshupMediaFile')).toBe(mockFile);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should handle missing media file', () => {
|
|
217
|
+
const payload = {
|
|
218
|
+
name: 'HAPTIC Test Template',
|
|
219
|
+
category: 'MARKETING',
|
|
220
|
+
content: 'Hello {{1}}',
|
|
221
|
+
wabaId: 'waba-123',
|
|
222
|
+
access_token: 'haptic-token',
|
|
223
|
+
PhoneId: 'phone-456',
|
|
224
|
+
hostName: 'hapticwhatsappbulk',
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
fetch.mockResolvedValueOnce({
|
|
228
|
+
status: 200,
|
|
229
|
+
json: () => Promise.resolve({ success: true }),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
createWhatsappTemplate({ payload });
|
|
233
|
+
|
|
234
|
+
const fetchCall = fetch.mock.calls[0];
|
|
235
|
+
const formData = fetchCall[1].body;
|
|
236
|
+
|
|
237
|
+
expect(formData.get('payload')).toBe(JSON.stringify(payload));
|
|
238
|
+
expect(formData.get('gupshupMediaFile')).toBe('undefined');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should handle array of media files', () => {
|
|
242
|
+
const payload = {
|
|
243
|
+
name: 'HAPTIC Test Template',
|
|
244
|
+
category: 'MARKETING',
|
|
245
|
+
content: 'Hello {{1}}',
|
|
246
|
+
wabaId: 'waba-123',
|
|
247
|
+
access_token: 'haptic-token',
|
|
248
|
+
PhoneId: 'phone-456',
|
|
249
|
+
hostName: 'hapticwhatsappbulk',
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const mockFiles = [
|
|
253
|
+
new File(['test1'], 'test1.jpg', { type: 'image/jpeg' }),
|
|
254
|
+
new File(['test2'], 'test2.jpg', { type: 'image/jpeg' }),
|
|
255
|
+
];
|
|
256
|
+
|
|
257
|
+
fetch.mockResolvedValueOnce({
|
|
258
|
+
status: 200,
|
|
259
|
+
json: () => Promise.resolve({ success: true }),
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
createWhatsappTemplate({ payload, gupshupMediaFile: mockFiles });
|
|
263
|
+
|
|
264
|
+
const fetchCall = fetch.mock.calls[0];
|
|
265
|
+
const formData = fetchCall[1].body;
|
|
266
|
+
|
|
267
|
+
expect(formData.get('gupshupMediaFile_0')).toBe(mockFiles[0]);
|
|
268
|
+
expect(formData.get('gupshupMediaFile_1')).toBe(mockFiles[1]);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Parameter validation', () => {
|
|
273
|
+
it('should validate required HAPTIC parameters', () => {
|
|
274
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
275
|
+
const whatsappParams = {
|
|
276
|
+
source: 'whatsapp',
|
|
277
|
+
wabaId: 'waba-123',
|
|
278
|
+
access_token: 'haptic-token',
|
|
279
|
+
phoneId: 'phone-456',
|
|
280
|
+
hostName: 'hapticwhatsappbulk',
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
fetch.mockResolvedValueOnce({
|
|
284
|
+
status: 200,
|
|
285
|
+
json: () => Promise.resolve({ success: true }),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
uploadFile({
|
|
289
|
+
file: mockFile,
|
|
290
|
+
assetType: 'whatsapp',
|
|
291
|
+
whatsappParams,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const fetchCall = fetch.mock.calls[0];
|
|
295
|
+
const formData = fetchCall[1].body;
|
|
296
|
+
|
|
297
|
+
// All required parameters should be present
|
|
298
|
+
expect(formData.get('wabaId')).toBe('waba-123');
|
|
299
|
+
expect(formData.get('accessToken')).toBe('haptic-token');
|
|
300
|
+
expect(formData.get('phoneId')).toBe('phone-456');
|
|
301
|
+
expect(formData.get('hostName')).toBe('hapticwhatsappbulk');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should handle mixed parameter types', () => {
|
|
305
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
306
|
+
const whatsappParams = {
|
|
307
|
+
source: 'whatsapp',
|
|
308
|
+
wabaId: 'waba-123',
|
|
309
|
+
access_token: 'haptic-token',
|
|
310
|
+
phoneId: '', // empty string
|
|
311
|
+
hostName: null, // null value
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
fetch.mockResolvedValueOnce({
|
|
315
|
+
status: 200,
|
|
316
|
+
json: () => Promise.resolve({ success: true }),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
uploadFile({
|
|
320
|
+
file: mockFile,
|
|
321
|
+
assetType: 'whatsapp',
|
|
322
|
+
whatsappParams,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const fetchCall = fetch.mock.calls[0];
|
|
326
|
+
const formData = fetchCall[1].body;
|
|
327
|
+
|
|
328
|
+
expect(formData.get('wabaId')).toBe('waba-123');
|
|
329
|
+
expect(formData.get('accessToken')).toBe('haptic-token');
|
|
330
|
+
expect(formData.get('phoneId')).toBe('');
|
|
331
|
+
expect(formData.get('hostName')).toBe('null');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('Error handling', () => {
|
|
336
|
+
it('should handle API errors gracefully', async () => {
|
|
337
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
338
|
+
const whatsappParams = {
|
|
339
|
+
source: 'whatsapp',
|
|
340
|
+
wabaId: 'waba-123',
|
|
341
|
+
access_token: 'haptic-token',
|
|
342
|
+
phoneId: 'phone-456',
|
|
343
|
+
hostName: 'hapticwhatsappbulk',
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
fetch.mockRejectedValueOnce(new Error('Network error'));
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
await uploadFile({
|
|
350
|
+
file: mockFile,
|
|
351
|
+
assetType: 'whatsapp',
|
|
352
|
+
whatsappParams,
|
|
353
|
+
});
|
|
354
|
+
} catch (error) {
|
|
355
|
+
expect(error.message).toBe('Network error');
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should handle HTTP error responses', async () => {
|
|
360
|
+
const mockFile = new File(['test content'], 'test.jpg', { type: 'image/jpeg' });
|
|
361
|
+
const whatsappParams = {
|
|
362
|
+
source: 'whatsapp',
|
|
363
|
+
wabaId: 'waba-123',
|
|
364
|
+
access_token: 'haptic-token',
|
|
365
|
+
phoneId: 'phone-456',
|
|
366
|
+
hostName: 'hapticwhatsappbulk',
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
fetch.mockResolvedValueOnce({
|
|
370
|
+
status: 400,
|
|
371
|
+
statusText: 'Bad Request',
|
|
372
|
+
json: () => Promise.resolve({ error: 'Invalid parameters' }),
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
await uploadFile({
|
|
377
|
+
file: mockFile,
|
|
378
|
+
assetType: 'whatsapp',
|
|
379
|
+
whatsappParams,
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
expect(error.status).toBe(400);
|
|
383
|
+
expect(error.statusText).toBe('Bad Request');
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
|
@@ -0,0 +1,322 @@
|
|
|
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, BIG_TEXT, BIG_PICTURE,
|
|
6
|
+
MANUAL_CAROUSEL,
|
|
7
|
+
AUTO_CAROUSEL,
|
|
8
|
+
FILMSTRIP_CAROUSEL,
|
|
9
|
+
ANDROID,
|
|
10
|
+
IOS,
|
|
11
|
+
} from "../v2Containers/MobilePushNew/constants";
|
|
12
|
+
import messages from '../v2Containers/MobilePushNew/messages';
|
|
13
|
+
|
|
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)
|
|
18
|
+
* - Supports platform-specific content (Android/iOS)
|
|
19
|
+
* - Handles CTA and custom fields
|
|
20
|
+
* @param {Object} params - The parameters for creating the payload
|
|
21
|
+
* @param {string} params.templateName - The name of the template
|
|
22
|
+
* @param {Object} params.androidContent - Android-specific content
|
|
23
|
+
* @param {Object} params.iosContent - iOS-specific content
|
|
24
|
+
* @param {Object} params.imageSrc - Image source URLs
|
|
25
|
+
* @param {Object} params.mpushVideoSrcAndPreview - Video source and preview URLs
|
|
26
|
+
* @param {Object} params.accountData - Account data for the payload
|
|
27
|
+
* @param {boolean} params.sameContent - Whether content is shared between platforms
|
|
28
|
+
* @param {Object} params.options - Additional options
|
|
29
|
+
* @param {Object} params.intl - Internationalization object for messages
|
|
30
|
+
*/
|
|
31
|
+
const createMobilePushPayload = ({
|
|
32
|
+
templateName,
|
|
33
|
+
androidContent,
|
|
34
|
+
iosContent,
|
|
35
|
+
imageSrc = {},
|
|
36
|
+
mpushVideoSrcAndPreview = {},
|
|
37
|
+
accountData,
|
|
38
|
+
sameContent = false,
|
|
39
|
+
options = {},
|
|
40
|
+
intl,
|
|
41
|
+
}) => {
|
|
42
|
+
// Platform support checks must be at the top
|
|
43
|
+
const isAndroidSupported = accountData?.configs?.android === '1';
|
|
44
|
+
const isIosSupported = accountData?.configs?.ios === '1';
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
accountId: accountIdFromAccountData, id, licenseCode: licenseCodeFromAccountData, sourceAccountIdentifier, sourceType: sourceTypeFromAccountData, sourceTypeName,
|
|
48
|
+
} = accountData || {};
|
|
49
|
+
// Validate account data
|
|
50
|
+
const accountId = accountIdFromAccountData || id;
|
|
51
|
+
const licenseCode = licenseCodeFromAccountData || sourceAccountIdentifier;
|
|
52
|
+
const sourceType = sourceTypeFromAccountData || sourceTypeName;
|
|
53
|
+
|
|
54
|
+
if (!accountId || !licenseCode || !sourceType) {
|
|
55
|
+
throw new Error(intl.formatMessage(messages.accountDataRequiredError));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Validate template name
|
|
59
|
+
const trimmedTemplateName = templateName?.trim();
|
|
60
|
+
if (!trimmedTemplateName) {
|
|
61
|
+
throw new Error(intl.formatMessage(messages.templateNameEmptyError));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate content
|
|
65
|
+
if (isAndroidSupported && (!androidContent?.title || !androidContent?.message)) {
|
|
66
|
+
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'Android' }));
|
|
67
|
+
}
|
|
68
|
+
if (isIosSupported && (!iosContent?.title || !iosContent?.message)) {
|
|
69
|
+
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'iOS' }));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Ensure imageSrc has the required properties
|
|
73
|
+
const safeImageSrc = {
|
|
74
|
+
androidImageSrc: imageSrc?.androidImageSrc || '',
|
|
75
|
+
iosImageSrc: imageSrc?.iosImageSrc || '',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Ensure mpushVideoSrcAndPreview has the required properties
|
|
79
|
+
const safeMpushVideoSrcAndPreview = {
|
|
80
|
+
mpushVideoSrc: mpushVideoSrcAndPreview?.mpushVideoSrc || '',
|
|
81
|
+
mpushVideoPreviewImg: mpushVideoSrcAndPreview?.mpushVideoPreviewImg || '',
|
|
82
|
+
duration: mpushVideoSrcAndPreview?.duration || 0,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Build the payload
|
|
86
|
+
const payload = {
|
|
87
|
+
name: trimmedTemplateName,
|
|
88
|
+
definition: {
|
|
89
|
+
accountId,
|
|
90
|
+
licenseCode,
|
|
91
|
+
sourceType,
|
|
92
|
+
sameContent,
|
|
93
|
+
mode: options?.mode,
|
|
94
|
+
},
|
|
95
|
+
versions: {
|
|
96
|
+
base: {},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Build Android content
|
|
101
|
+
if (isAndroidSupported) {
|
|
102
|
+
payload.versions.base.ANDROID = buildPlatformContent(
|
|
103
|
+
androidContent,
|
|
104
|
+
safeImageSrc.androidImageSrc,
|
|
105
|
+
safeMpushVideoSrcAndPreview,
|
|
106
|
+
ANDROID,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Build iOS content
|
|
111
|
+
if (isIosSupported) {
|
|
112
|
+
payload.versions.base.IOS = buildPlatformContent(
|
|
113
|
+
iosContent,
|
|
114
|
+
safeImageSrc.iosImageSrc,
|
|
115
|
+
safeMpushVideoSrcAndPreview,
|
|
116
|
+
IOS,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return payload;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Builds platform-specific content for the payload
|
|
125
|
+
* @param {Object} content - Platform content (Android/iOS)
|
|
126
|
+
* @param {string} imageSrc - Image source URL
|
|
127
|
+
* @param {Object} mpushVideoSrcAndPreview - Video source and preview URLs
|
|
128
|
+
* @param {string} platform - Platform (ANDROID/IOS)
|
|
129
|
+
* @returns {Object} Platform-specific content
|
|
130
|
+
*/
|
|
131
|
+
function buildPlatformContent(content, imageSrc, mpushVideoSrcAndPreview, platform) {
|
|
132
|
+
const {
|
|
133
|
+
title = '',
|
|
134
|
+
message = '',
|
|
135
|
+
expandableDetails = {},
|
|
136
|
+
actionOnClick,
|
|
137
|
+
linkType,
|
|
138
|
+
deepLinkValue,
|
|
139
|
+
externalLinkValue,
|
|
140
|
+
} = content || {};
|
|
141
|
+
|
|
142
|
+
const platformContent = {
|
|
143
|
+
title,
|
|
144
|
+
message,
|
|
145
|
+
expandableDetails: { ...expandableDetails },
|
|
146
|
+
custom: buildCustomFields(content),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Handle CTA
|
|
150
|
+
if (actionOnClick) {
|
|
151
|
+
platformContent.cta = {
|
|
152
|
+
type: linkType === DEEP_LINK ? DEEP_LINK : EXTERNAL_URL,
|
|
153
|
+
actionLink: linkType === DEEP_LINK ? deepLinkValue : externalLinkValue,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Handle media types
|
|
158
|
+
const style = content?.mediaType || expandableDetails?.style;
|
|
159
|
+
|
|
160
|
+
// Handle BIG_PICTURE media type
|
|
161
|
+
if (style === BIG_PICTURE || style === IMAGE) {
|
|
162
|
+
platformContent.expandableDetails.style = BIG_PICTURE;
|
|
163
|
+
platformContent.expandableDetails.image = imageSrc;
|
|
164
|
+
} else if (style === VIDEO) { // Handle VIDEO media type
|
|
165
|
+
platformContent.expandableDetails.style = VIDEO;
|
|
166
|
+
platformContent.expandableDetails.media = [{
|
|
167
|
+
url: mpushVideoSrcAndPreview.mpushVideoSrc || (content?.mediaList?.[0]?.url || ''),
|
|
168
|
+
text: content?.mediaList?.[0]?.text || '',
|
|
169
|
+
type: VIDEO,
|
|
170
|
+
}];
|
|
171
|
+
} else if (style === GIF) { // Handle GIF media type
|
|
172
|
+
platformContent.expandableDetails.style = GIF;
|
|
173
|
+
if (content?.mediaList?.[0]) {
|
|
174
|
+
// If mediaList exists, use it and preserve the original type
|
|
175
|
+
const { url = '', text = '', type = GIF } = content.mediaList[0];
|
|
176
|
+
platformContent.expandableDetails.media = [{
|
|
177
|
+
url,
|
|
178
|
+
text,
|
|
179
|
+
type: type.toLowerCase(),
|
|
180
|
+
}];
|
|
181
|
+
} else {
|
|
182
|
+
// If no mediaList but mpushVideoSrc exists, convert to VIDEO
|
|
183
|
+
platformContent.expandableDetails.media = [{
|
|
184
|
+
url: mpushVideoSrcAndPreview.mpushVideoSrc,
|
|
185
|
+
text: '',
|
|
186
|
+
type: GIF, // Backend expects GIF type for GIFs
|
|
187
|
+
}];
|
|
188
|
+
}
|
|
189
|
+
} else if ([CAROUSEL, MANUAL_CAROUSEL, AUTO_CAROUSEL, FILMSTRIP_CAROUSEL].includes(style)) { // Handle CAROUSEL media types
|
|
190
|
+
platformContent.expandableDetails.style = MANUAL_CAROUSEL;
|
|
191
|
+
|
|
192
|
+
// Handle carouselData
|
|
193
|
+
if (content?.carouselData?.length > 0) {
|
|
194
|
+
platformContent.expandableDetails.carouselData = content.carouselData.map((card) => {
|
|
195
|
+
const {
|
|
196
|
+
mediaType,
|
|
197
|
+
imageUrl,
|
|
198
|
+
videoSrc,
|
|
199
|
+
buttons,
|
|
200
|
+
} = card || {};
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
mediaType: mediaType || IMAGE.toLowerCase(),
|
|
204
|
+
imageUrl: imageUrl || '',
|
|
205
|
+
videoSrc: videoSrc || '',
|
|
206
|
+
buttons: buttons ? buttons.map((button) => {
|
|
207
|
+
const { linkType: buttonLinkType, deepLinkKeys, deepLinkValue: buttonDeepLinkValue } = button || {};
|
|
208
|
+
if (buttonLinkType === DEEP_LINK && deepLinkKeys && buttonDeepLinkValue) {
|
|
209
|
+
const deepLinkKeysArray = Array.isArray(deepLinkKeys) ? deepLinkKeys : [deepLinkKeys];
|
|
210
|
+
const updatedDeepLinkValue = appendDeepLinkKeysToUrl(buttonDeepLinkValue, deepLinkKeysArray);
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
...button,
|
|
214
|
+
deepLinkValue: updatedDeepLinkValue,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return button;
|
|
218
|
+
}) : [],
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Handle mediaList
|
|
224
|
+
if (content?.mediaList && Array.isArray(content.mediaList) && content.mediaList.length > 0) {
|
|
225
|
+
platformContent.expandableDetails.media = content.mediaList.map((media) => {
|
|
226
|
+
const {
|
|
227
|
+
url,
|
|
228
|
+
text,
|
|
229
|
+
type,
|
|
230
|
+
} = media || {};
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
url,
|
|
234
|
+
text: text || '',
|
|
235
|
+
type,
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
} else { // Handle BIG_TEXT media type (default)
|
|
240
|
+
platformContent.expandableDetails.style = BIG_TEXT;
|
|
241
|
+
platformContent.expandableDetails.message = message;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Handle GIF media type
|
|
245
|
+
if (content?.mediaType === GIF) {
|
|
246
|
+
platformContent.expandableDetails = {
|
|
247
|
+
media: [{
|
|
248
|
+
url: mpushVideoSrcAndPreview?.mpushVideoSrc || (content?.mediaList?.[0]?.url || ''),
|
|
249
|
+
text: content?.mediaList?.[0]?.text || '',
|
|
250
|
+
type: GIF,
|
|
251
|
+
}],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// iOS-specific handling
|
|
256
|
+
if (platform === IOS) {
|
|
257
|
+
// iOS always needs a ctas array in expandableDetails
|
|
258
|
+
platformContent.expandableDetails.ctas = platformContent.expandableDetails.ctas || [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return platformContent;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Builds custom fields array from content
|
|
266
|
+
* @param {Object} content - Platform content (Android/iOS)
|
|
267
|
+
* @returns {Array} Array of custom fields
|
|
268
|
+
*/
|
|
269
|
+
function buildCustomFields(content) {
|
|
270
|
+
let customFields = [];
|
|
271
|
+
const { custom = {} } = content || {};
|
|
272
|
+
// If content.custom is already an array, use it as base
|
|
273
|
+
if (Array.isArray(custom)) {
|
|
274
|
+
customFields = [...custom];
|
|
275
|
+
} else if (custom && typeof custom === 'object') {
|
|
276
|
+
// If it's an object, map to array
|
|
277
|
+
customFields = Object.entries(custom).map(([key, value]) => ({ key, value }));
|
|
278
|
+
}
|
|
279
|
+
const { deepLinkKeysValue, deepLinkValue, linkType } = custom || {};
|
|
280
|
+
// Add deepLinkKeys to custom fields if it exists
|
|
281
|
+
if (deepLinkKeysValue && linkType === DEEP_LINK) {
|
|
282
|
+
if (Array.isArray(deepLinkKeysValue)) {
|
|
283
|
+
// Handle array of deep link keys
|
|
284
|
+
deepLinkKeysValue.forEach((key) => {
|
|
285
|
+
if (key) {
|
|
286
|
+
customFields.push({
|
|
287
|
+
key,
|
|
288
|
+
value: deepLinkValue,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
} else {
|
|
293
|
+
// Handle single deep link key
|
|
294
|
+
customFields.push({
|
|
295
|
+
key: deepLinkKeysValue,
|
|
296
|
+
value: deepLinkValue,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return customFields;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Appends deep link keys to URL
|
|
306
|
+
* @param {string} url - Base URL
|
|
307
|
+
* @param {Array} keys - Deep link keys
|
|
308
|
+
* @returns {string} URL with appended keys
|
|
309
|
+
*/
|
|
310
|
+
function appendDeepLinkKeysToUrl(url, keys) {
|
|
311
|
+
if (!url || !keys || !Array.isArray(keys) || keys?.length === 0) return url;
|
|
312
|
+
|
|
313
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
314
|
+
const validKeys = keys.filter((key) => typeof key === 'string' && key?.length > 0);
|
|
315
|
+
if (validKeys?.length === 0) return url;
|
|
316
|
+
|
|
317
|
+
const keyParams = validKeys.map((key) => `${key}={{${key}}}`).join('&');
|
|
318
|
+
|
|
319
|
+
return `${url}${separator}${keyParams}`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export default createMobilePushPayload;
|