@capillarytech/creatives-library 8.0.153 → 8.0.154
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/config/app.js +1 -1
- package/index.js +1 -9
- package/package.json +2 -2
- package/services/api.js +2 -6
- package/services/tests/api.test.js +1 -26
- package/services/tests/haptic-api.test.js +7 -0
- package/services/tests/mockData.js +1 -0
- package/v2Containers/CreativesContainer/index.js +2 -3
- package/v2Containers/Templates/tests/mockData.js +1 -0
- package/v2Containers/Whatsapp/index.js +29 -1
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +597 -0
- package/v2Containers/Whatsapp/tests/actions.test.js +1 -0
- package/v2Containers/Whatsapp/tests/haptic.test.js +0 -1
- package/v2Containers/Whatsapp/tests/index.test.js +35 -5
- package/v2Containers/mockdata.js +5 -3
- package/assets/loading_img.gif +0 -0
- package/utils/tests/transformTemplateConfig.test.js +0 -290
- package/utils/transformTemplateConfig.js +0 -253
- package/utils/whatsappMediaUtils.js +0 -44
- package/v2Containers/Email/tests/index.test.js +0 -35
|
@@ -84,6 +84,21 @@ describe('Creatives Whatsapp test1/>', () => {
|
|
|
84
84
|
loadingTags={args.loadingTags}
|
|
85
85
|
metaEntities={[]}
|
|
86
86
|
getDefaultTags={true}
|
|
87
|
+
Templates={{
|
|
88
|
+
senderDetails: {
|
|
89
|
+
status: 'SUCCESS',
|
|
90
|
+
domainProperties: [
|
|
91
|
+
{
|
|
92
|
+
domainProperties: {
|
|
93
|
+
connectionProperties: {
|
|
94
|
+
sourceAccountIdentifier: '2000222347',
|
|
95
|
+
baseUrl: 'https://api.gupshup.io/whatsapp/v1',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
}}
|
|
87
102
|
/>
|
|
88
103
|
</Provider>,
|
|
89
104
|
);
|
|
@@ -240,7 +255,7 @@ describe('Creatives Whatsapp test1/>', () => {
|
|
|
240
255
|
|
|
241
256
|
it('test addVariable in create mode', () => {
|
|
242
257
|
|
|
243
|
-
|
|
258
|
+
|
|
244
259
|
renderHelper({});
|
|
245
260
|
renderedComponent.update();
|
|
246
261
|
renderedComponent.find('[data-testid="suffix-button"]').at(1).props().onClick();
|
|
@@ -365,6 +380,21 @@ describe('Creatives Whatsapp test2/>', () => {
|
|
|
365
380
|
loadingTags={args.loadingTags}
|
|
366
381
|
metaEntities={[]}
|
|
367
382
|
getDefaultTags={true}
|
|
383
|
+
Templates={{
|
|
384
|
+
senderDetails: {
|
|
385
|
+
status: 'SUCCESS',
|
|
386
|
+
domainProperties: [
|
|
387
|
+
{
|
|
388
|
+
domainProperties: {
|
|
389
|
+
connectionProperties: {
|
|
390
|
+
sourceAccountIdentifier: '2000222347',
|
|
391
|
+
baseUrl: 'https://api.gupshup.io/whatsapp/v1',
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
}}
|
|
368
398
|
/>
|
|
369
399
|
</Provider>,
|
|
370
400
|
);
|
|
@@ -530,7 +560,7 @@ describe('mediaTypeOptions', () => {
|
|
|
530
560
|
templateCategory: WHATSAPP_CATEGORIES.marketing,
|
|
531
561
|
});
|
|
532
562
|
const hasCarousel = options.some(
|
|
533
|
-
(opt) => Array.isArray(opt)
|
|
563
|
+
(opt) => Array.isArray(opt)
|
|
534
564
|
? opt.some((o) => o.key === 'CAROUSEL')
|
|
535
565
|
: opt.key === 'CAROUSEL'
|
|
536
566
|
) || options.flat().some((o) => o.key === 'CAROUSEL');
|
|
@@ -543,7 +573,7 @@ describe('mediaTypeOptions', () => {
|
|
|
543
573
|
templateCategory: WHATSAPP_CATEGORIES.marketing,
|
|
544
574
|
});
|
|
545
575
|
const hasCarousel = options.some(
|
|
546
|
-
(opt) => Array.isArray(opt)
|
|
576
|
+
(opt) => Array.isArray(opt)
|
|
547
577
|
? opt.some((o) => o.key === 'CAROUSEL')
|
|
548
578
|
: opt.key === 'CAROUSEL'
|
|
549
579
|
) || options.flat().some((o) => o.key === 'CAROUSEL');
|
|
@@ -556,7 +586,7 @@ describe('mediaTypeOptions', () => {
|
|
|
556
586
|
templateCategory: WHATSAPP_CATEGORIES.utility,
|
|
557
587
|
});
|
|
558
588
|
const hasCarousel = options.some(
|
|
559
|
-
(opt) => Array.isArray(opt)
|
|
589
|
+
(opt) => Array.isArray(opt)
|
|
560
590
|
? opt.some((o) => o.key === 'CAROUSEL')
|
|
561
591
|
: opt.key === 'CAROUSEL'
|
|
562
592
|
) || options.flat().some((o) => o.key === 'CAROUSEL');
|
|
@@ -569,7 +599,7 @@ describe('mediaTypeOptions', () => {
|
|
|
569
599
|
templateCategory: WHATSAPP_CATEGORIES.utility,
|
|
570
600
|
});
|
|
571
601
|
const hasCarousel = options.some(
|
|
572
|
-
(opt) => Array.isArray(opt)
|
|
602
|
+
(opt) => Array.isArray(opt)
|
|
573
603
|
? opt.some((o) => o.key === 'CAROUSEL')
|
|
574
604
|
: opt.key === 'CAROUSEL'
|
|
575
605
|
) || options.flat().some((o) => o.key === 'CAROUSEL');
|
package/v2Containers/mockdata.js
CHANGED
|
@@ -85,7 +85,8 @@ export default {
|
|
|
85
85
|
"authToken": "Bearer abcd",
|
|
86
86
|
"sourceAccountIdentifier": "12345",
|
|
87
87
|
"wabaId": "12345",
|
|
88
|
-
"whatsapp_pool_id": "CAP001"
|
|
88
|
+
"whatsapp_pool_id": "CAP001",
|
|
89
|
+
"baseUrl": "https://api.whatsapp.com/v1"
|
|
89
90
|
},
|
|
90
91
|
"hostName": "karixwhatsappbulk"
|
|
91
92
|
},
|
|
@@ -128,7 +129,8 @@ export default {
|
|
|
128
129
|
"connectionProperties": {
|
|
129
130
|
"password": "qrtCAJCz",
|
|
130
131
|
"sourceAccountIdentifier": "123456",
|
|
131
|
-
"userid": "123456"
|
|
132
|
+
"userid": "123456",
|
|
133
|
+
"baseUrl": "https://api.gupshup.io/whatsapp/v1"
|
|
132
134
|
},
|
|
133
135
|
"hostName": "gupshupwhatsappbulk"
|
|
134
136
|
},
|
|
@@ -2587,7 +2589,7 @@ export default {
|
|
|
2587
2589
|
loyaltyMetaData: {
|
|
2588
2590
|
actionName: "SEND_COMMUNICATION_ACTION",
|
|
2589
2591
|
},
|
|
2590
|
-
getWhatsappAutoFillData:
|
|
2592
|
+
getWhatsappAutoFillData:
|
|
2591
2593
|
< CapLabel
|
|
2592
2594
|
className="whatsapp-autofill-btn"
|
|
2593
2595
|
type="label21"
|
package/assets/loading_img.gif
DELETED
|
Binary file
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
transformTemplateConfigWithMediaDetails,
|
|
4
|
-
convertMediaTagsToUrls,
|
|
5
|
-
convertUrlsToMediaTags,
|
|
6
|
-
TRANSFORM_DIRECTION,
|
|
7
|
-
MEDIA_CONTENT_TYPE,
|
|
8
|
-
} from '../transformTemplateConfig';
|
|
9
|
-
import { getMediaDetails } from '../../services/api';
|
|
10
|
-
import { WHATSAPP } from '../../v2Containers/Whatsapp/constants';
|
|
11
|
-
|
|
12
|
-
jest.mock('../../services/api', () => ({
|
|
13
|
-
getMediaDetails: jest.fn(),
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
const mockUrlToIdMapping = {
|
|
17
|
-
'http://example.com/image1.png': 'id1',
|
|
18
|
-
'http://example.com/video.mp4': 'id2',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const mockIdToUrlMapping = {
|
|
22
|
-
id1: 'http://example.com/image1.png',
|
|
23
|
-
id2: 'http://example.com/video.mp4',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
describe('transformTemplateConfig', () => {
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
getMediaDetails.mockClear();
|
|
29
|
-
// eslint-disable-next-line no-console
|
|
30
|
-
console.warn = jest.fn();
|
|
31
|
-
// eslint-disable-next-line no-console
|
|
32
|
-
console.error = jest.fn();
|
|
33
|
-
// eslint-disable-next-line no-console
|
|
34
|
-
console.log = jest.fn();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('transformTemplateConfigWithMediaDetails', () => {
|
|
38
|
-
it('should return original config for non-whatsapp channel', async () => {
|
|
39
|
-
const templateConfig = { cards: [{ media: { url: 'some_url' } }] };
|
|
40
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
41
|
-
templateConfig,
|
|
42
|
-
'SMS',
|
|
43
|
-
'media123',
|
|
44
|
-
);
|
|
45
|
-
expect(result).toEqual(templateConfig);
|
|
46
|
-
expect(getMediaDetails).not.toHaveBeenCalled();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should return original config if no cards are present', async () => {
|
|
50
|
-
const templateConfig = { cards: [] };
|
|
51
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
52
|
-
templateConfig,
|
|
53
|
-
WHATSAPP,
|
|
54
|
-
'media123',
|
|
55
|
-
);
|
|
56
|
-
expect(result).toEqual(templateConfig);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should return original config and warn if mediaDetailsId is missing', async () => {
|
|
60
|
-
const templateConfig = { cards: [{ media: { url: 'some_url' } }] };
|
|
61
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
62
|
-
templateConfig,
|
|
63
|
-
WHATSAPP,
|
|
64
|
-
null,
|
|
65
|
-
);
|
|
66
|
-
expect(result).toEqual(templateConfig);
|
|
67
|
-
// eslint-disable-next-line no-console
|
|
68
|
-
expect(console.warn).toHaveBeenCalledWith(
|
|
69
|
-
'mediaDetailsId is required for transformTemplateConfigWithMediaDetails',
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should handle forward transformation (URL to Tag)', async () => {
|
|
74
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
75
|
-
const templateConfig = {
|
|
76
|
-
cards: [
|
|
77
|
-
{ media: { url: 'http://example.com/image1.png' } },
|
|
78
|
-
{ media: { url: 'http://example.com/video.mp4' } },
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
83
|
-
templateConfig,
|
|
84
|
-
WHATSAPP,
|
|
85
|
-
'media123',
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
expect(result.cards[0].media.url).toBe('{{media_content(id1)}}');
|
|
89
|
-
expect(result.cards[1].media.url).toBe('{{media_content(id2)}}');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should handle reverse transformation (Tag to URL)', async () => {
|
|
93
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
94
|
-
const templateConfig = {
|
|
95
|
-
cards: [
|
|
96
|
-
{ media: { url: '{{media_content(id1)}}' } },
|
|
97
|
-
{ media: { url: '{{media_content(id2)}}' } },
|
|
98
|
-
],
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
102
|
-
templateConfig,
|
|
103
|
-
WHATSAPP,
|
|
104
|
-
'media123',
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
expect(result.cards[0].media.url).toBe(mockIdToUrlMapping.id1);
|
|
108
|
-
expect(result.cards[1].media.url).toBe(mockIdToUrlMapping.id2);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should handle smart transformation for mixed content', async () => {
|
|
112
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
113
|
-
const templateConfig = {
|
|
114
|
-
cards: [
|
|
115
|
-
{ media: { url: 'http://example.com/image1.png' } }, // URL
|
|
116
|
-
{ media: { url: '{{media_content(id2)}}' } }, // Tag
|
|
117
|
-
],
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
121
|
-
templateConfig,
|
|
122
|
-
WHATSAPP,
|
|
123
|
-
'media123',
|
|
124
|
-
);
|
|
125
|
-
|
|
126
|
-
expect(result.cards[0].media.url).toBe('{{media_content(id1)}}');
|
|
127
|
-
expect(result.cards[1].media.url).toBe(mockIdToUrlMapping.id2);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it('should force forward transformation', async () => {
|
|
131
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
132
|
-
const templateConfig = {
|
|
133
|
-
cards: [{ media: { url: 'http://example.com/image1.png' } }],
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
137
|
-
templateConfig,
|
|
138
|
-
WHATSAPP,
|
|
139
|
-
'media123',
|
|
140
|
-
{ forceDirection: TRANSFORM_DIRECTION.FORWARD },
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
expect(result.cards[0].media.url).toBe('{{media_content(id1)}}');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should force reverse transformation', async () => {
|
|
147
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
148
|
-
const templateConfig = {
|
|
149
|
-
cards: [{ media: { url: '{{media_content(id1)}}' } }],
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
153
|
-
templateConfig,
|
|
154
|
-
WHATSAPP,
|
|
155
|
-
'media123',
|
|
156
|
-
{ forceDirection: TRANSFORM_DIRECTION.REVERSE },
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
expect(result.cards[0].media.url).toBe(mockIdToUrlMapping.id1);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should handle API failure gracefully', async () => {
|
|
163
|
-
const error = new Error('API Error');
|
|
164
|
-
getMediaDetails.mockRejectedValue(error);
|
|
165
|
-
const templateConfig = {
|
|
166
|
-
cards: [{ media: { url: 'http://example.com/image1.png' } }],
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
170
|
-
templateConfig,
|
|
171
|
-
WHATSAPP,
|
|
172
|
-
'media123',
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// Fallback logic does not transform
|
|
176
|
-
expect(result.cards[0].media.url).toBe('http://example.com/image1.png');
|
|
177
|
-
// eslint-disable-next-line no-console
|
|
178
|
-
expect(console.error).toHaveBeenCalledWith(
|
|
179
|
-
'Failed to fetch media details for transformation:',
|
|
180
|
-
error,
|
|
181
|
-
);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('should warn when no media mapping is found for a URL', async () => {
|
|
185
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
186
|
-
const templateConfig = {
|
|
187
|
-
cards: [{ media: { url: 'http://example.com/unmapped.jpg' } }],
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
await transformTemplateConfigWithMediaDetails(
|
|
191
|
-
templateConfig,
|
|
192
|
-
WHATSAPP,
|
|
193
|
-
'media123',
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
// eslint-disable-next-line no-console
|
|
197
|
-
expect(console.warn).toHaveBeenCalledWith(
|
|
198
|
-
'No media detail ID found for URL: http://example.com/unmapped.jpg',
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('should warn when no URL is found for a media ID', async () => {
|
|
203
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
204
|
-
const templateConfig = {
|
|
205
|
-
cards: [{ media: { url: '{{media_content(unmapped_id)}}' } }],
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
await transformTemplateConfigWithMediaDetails(
|
|
209
|
-
templateConfig,
|
|
210
|
-
WHATSAPP,
|
|
211
|
-
'media123',
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
// eslint-disable-next-line no-console
|
|
215
|
-
expect(console.warn).toHaveBeenCalledWith(
|
|
216
|
-
'No URL found for media ID: unmapped_id',
|
|
217
|
-
);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('should handle cards with non-string media url gracefully', async () => {
|
|
221
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
222
|
-
const templateConfig = {
|
|
223
|
-
cards: [
|
|
224
|
-
{ media: { url: 123 } },
|
|
225
|
-
],
|
|
226
|
-
};
|
|
227
|
-
const result = await transformTemplateConfigWithMediaDetails(templateConfig, WHATSAPP, 'media123');
|
|
228
|
-
expect(result.cards[0].media.url).toBe(123);
|
|
229
|
-
expect(console.warn).toHaveBeenCalledWith('No media detail ID found for URL: 123');
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it('should use fallback to extract media id when API fails for forced forward transform', async () => {
|
|
233
|
-
getMediaDetails.mockRejectedValue(new Error('API Error'));
|
|
234
|
-
const templateConfig = {
|
|
235
|
-
cards: [{ media: { url: '{{media_content(fallback_id)}}' } }],
|
|
236
|
-
};
|
|
237
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
238
|
-
templateConfig,
|
|
239
|
-
WHATSAPP,
|
|
240
|
-
'media123',
|
|
241
|
-
{ forceDirection: TRANSFORM_DIRECTION.FORWARD },
|
|
242
|
-
);
|
|
243
|
-
expect(result.cards[0].media.url).toBe('{{media_content(fallback_id)}}');
|
|
244
|
-
expect(console.error).toHaveBeenCalledWith(
|
|
245
|
-
'Failed to fetch media details for transformation:',
|
|
246
|
-
expect.any(Error),
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('should return original config if media details mapping is empty', async () => {
|
|
251
|
-
getMediaDetails.mockResolvedValue({ response: {} });
|
|
252
|
-
const templateConfig = {
|
|
253
|
-
cards: [{ media: { url: 'http://example.com/image1.png' } }],
|
|
254
|
-
};
|
|
255
|
-
const result = await transformTemplateConfigWithMediaDetails(
|
|
256
|
-
templateConfig,
|
|
257
|
-
WHATSAPP,
|
|
258
|
-
'media123',
|
|
259
|
-
);
|
|
260
|
-
expect(result).toEqual(templateConfig);
|
|
261
|
-
expect(console.warn).toHaveBeenCalledWith('No media details mapping found in API response');
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
describe('convenience wrappers', () => {
|
|
266
|
-
it('convertUrlsToMediaTags should force forward direction', async () => {
|
|
267
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
268
|
-
const templateConfig = {
|
|
269
|
-
cards: [{ media: { url: 'http://example.com/image1.png' } }],
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const result = await convertUrlsToMediaTags(templateConfig, WHATSAPP, 'media123');
|
|
273
|
-
|
|
274
|
-
expect(getMediaDetails).toHaveBeenCalledWith({ id: 'media123' });
|
|
275
|
-
expect(result.cards[0].media.url).toBe('{{media_content(id1)}}');
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('convertMediaTagsToUrls should force reverse direction', async () => {
|
|
279
|
-
getMediaDetails.mockResolvedValue({ response: mockUrlToIdMapping });
|
|
280
|
-
const templateConfig = {
|
|
281
|
-
cards: [{ media: { url: '{{media_content(id1)}}' } }],
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const result = await convertMediaTagsToUrls(templateConfig, WHATSAPP, 'media123');
|
|
285
|
-
|
|
286
|
-
expect(getMediaDetails).toHaveBeenCalledWith({ id: 'media123' });
|
|
287
|
-
expect(result.cards[0].media.url).toBe(mockIdToUrlMapping.id1);
|
|
288
|
-
});
|
|
289
|
-
});
|
|
290
|
-
});
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import cloneDeep from 'lodash/cloneDeep';
|
|
2
|
-
import { getMediaDetails } from '../services/api';
|
|
3
|
-
import { WHATSAPP } from '../v2Containers/Whatsapp/constants';
|
|
4
|
-
|
|
5
|
-
// Enum-like constants to avoid magic strings
|
|
6
|
-
export const MEDIA_CONTENT_TYPE = {
|
|
7
|
-
MIXED: 'mixed',
|
|
8
|
-
TAGS: 'tags',
|
|
9
|
-
URLS: 'urls',
|
|
10
|
-
NONE: 'none',
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const TRANSFORM_DIRECTION = {
|
|
14
|
-
FORWARD: 'forward',
|
|
15
|
-
REVERSE: 'reverse',
|
|
16
|
-
SMART: 'smart',
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Helper function to extract media ID from URL patterns
|
|
21
|
-
* @param {string} url - The media URL to extract ID from
|
|
22
|
-
* @returns {string|null} - Extracted media ID or null if not found
|
|
23
|
-
*/
|
|
24
|
-
function extractMediaIdFromUrl(url) {
|
|
25
|
-
if (!url || typeof url !== 'string') {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// Already contains media_content pattern - extract the ID
|
|
31
|
-
const mediaContentMatch = url.match(/\{\{media_content\(([^)]+)\)\}\}/);
|
|
32
|
-
if (mediaContentMatch) {
|
|
33
|
-
return mediaContentMatch[1];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Helper function to detect if template config contains media_content tags or URLs
|
|
41
|
-
* @param {object} templateConfig The template configuration to analyze
|
|
42
|
-
* @returns {string} MEDIA_CONTENT_TYPE.TAGS if contains media_content tags, MEDIA_CONTENT_TYPE.URLS if contains URLs, MEDIA_CONTENT_TYPE.MIXED if both, MEDIA_CONTENT_TYPE.NONE if neither
|
|
43
|
-
*/
|
|
44
|
-
function detectMediaContentType(templateConfig) {
|
|
45
|
-
if (!templateConfig?.cards?.length) {
|
|
46
|
-
return MEDIA_CONTENT_TYPE.NONE;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let hasUrls = false;
|
|
50
|
-
let hasTags = false;
|
|
51
|
-
|
|
52
|
-
templateConfig.cards.forEach(card => {
|
|
53
|
-
if (card?.media?.url) {
|
|
54
|
-
const url = card.media.url;
|
|
55
|
-
if (extractMediaIdFromTag(url)) {
|
|
56
|
-
hasTags = true;
|
|
57
|
-
} else if (extractMediaIdFromUrl(url)) {
|
|
58
|
-
hasUrls = true;
|
|
59
|
-
} else {
|
|
60
|
-
// Regular URL that doesn't contain extractable media ID
|
|
61
|
-
hasUrls = true;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
if (hasUrls && hasTags) return MEDIA_CONTENT_TYPE.MIXED;
|
|
67
|
-
if (hasTags) return MEDIA_CONTENT_TYPE.TAGS;
|
|
68
|
-
if (hasUrls) return MEDIA_CONTENT_TYPE.URLS;
|
|
69
|
-
return MEDIA_CONTENT_TYPE.NONE;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Smart bidirectional transformation function that automatically detects and applies the correct transformation.
|
|
74
|
-
* This is the recommended function to use when you have a media details ID.
|
|
75
|
-
*
|
|
76
|
-
* AUTOMATIC DETECTION:
|
|
77
|
-
* - If template contains URLs → converts to {{media_content(id)}} tags
|
|
78
|
-
* - If template contains {{media_content(id)}} tags → converts to URLs
|
|
79
|
-
* - If template contains mixed content → applies appropriate transformation per card
|
|
80
|
-
*
|
|
81
|
-
* @param {object} templateConfig The template configuration to transform.
|
|
82
|
-
* @param {string} channel The channel for which to transform the config.
|
|
83
|
-
* @param {string} mediaDetailsId The ID to use when calling getMediaDetails API.
|
|
84
|
-
* @param {object} options Additional options for the transformation.
|
|
85
|
-
* @param {string} options.forceDirection Force transformation direction: 'forward' (URLs→tags) or 'reverse' (tags→URLs).
|
|
86
|
-
* @returns {Promise<object>} Promise that resolves to the transformed template configuration.
|
|
87
|
-
*/
|
|
88
|
-
export async function transformTemplateConfigWithMediaDetails(templateConfig, channel, mediaDetailsId, options = {}) {
|
|
89
|
-
if (channel !== WHATSAPP || !templateConfig?.cards?.length) {
|
|
90
|
-
return templateConfig;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!mediaDetailsId) {
|
|
94
|
-
console.warn('mediaDetailsId is required for transformTemplateConfigWithMediaDetails');
|
|
95
|
-
return templateConfig;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const { forceDirection = null } = options;
|
|
99
|
-
|
|
100
|
-
// Detect what type of content we have
|
|
101
|
-
const contentType = detectMediaContentType(templateConfig);
|
|
102
|
-
|
|
103
|
-
if (contentType === MEDIA_CONTENT_TYPE.NONE) {
|
|
104
|
-
console.log('No media content detected, returning original config');
|
|
105
|
-
return templateConfig;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Determine transformation direction
|
|
109
|
-
let transformDirection;
|
|
110
|
-
if (forceDirection) {
|
|
111
|
-
transformDirection = forceDirection;
|
|
112
|
-
console.log(`Forced transformation direction: ${transformDirection}`);
|
|
113
|
-
} else {
|
|
114
|
-
// Auto-detect based on content
|
|
115
|
-
if (contentType === MEDIA_CONTENT_TYPE.URLS) {
|
|
116
|
-
transformDirection = TRANSFORM_DIRECTION.FORWARD; // URLs → tags
|
|
117
|
-
console.log('Detected URLs, applying forward transformation (URLs → tags)');
|
|
118
|
-
} else if (contentType === MEDIA_CONTENT_TYPE.TAGS) {
|
|
119
|
-
transformDirection = TRANSFORM_DIRECTION.REVERSE; // tags → URLs
|
|
120
|
-
console.log('Detected media_content tags, applying reverse transformation (tags → URLs)');
|
|
121
|
-
} else if (contentType === MEDIA_CONTENT_TYPE.MIXED) {
|
|
122
|
-
transformDirection = TRANSFORM_DIRECTION.SMART; // Handle mixed content intelligently
|
|
123
|
-
console.log('Detected mixed content, applying smart transformation');
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const newConfig = cloneDeep(templateConfig);
|
|
128
|
-
try {
|
|
129
|
-
console.log(`Fetching media details for ${transformDirection} transformation, ID: ${mediaDetailsId}`);
|
|
130
|
-
|
|
131
|
-
// Fetch media details mapping from API
|
|
132
|
-
const response = await getMediaDetails({ id: mediaDetailsId })
|
|
133
|
-
|
|
134
|
-
// Extract the URL-to-ID mapping from the API response
|
|
135
|
-
const urlToIdMapping = response?.response || {};
|
|
136
|
-
|
|
137
|
-
if (Object.keys(urlToIdMapping).length === 0) {
|
|
138
|
-
console.warn('No media details mapping found in API response');
|
|
139
|
-
return newConfig;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Create reverse mapping: ID -> URL (for reverse transformations)
|
|
143
|
-
const idToUrlMapping = {};
|
|
144
|
-
Object.entries(urlToIdMapping).forEach(([url, mediaId]) => {
|
|
145
|
-
idToUrlMapping[mediaId] = url;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
console.log(`Processing ${Object.keys(urlToIdMapping).length} media mappings`);
|
|
149
|
-
|
|
150
|
-
// Apply transformations based on direction
|
|
151
|
-
newConfig?.cards?.forEach(card => {
|
|
152
|
-
if (card?.media && card?.media?.url) {
|
|
153
|
-
const currentUrl = card.media.url;
|
|
154
|
-
|
|
155
|
-
if (transformDirection === TRANSFORM_DIRECTION.FORWARD ||
|
|
156
|
-
(transformDirection === TRANSFORM_DIRECTION.SMART && !extractMediaIdFromTag(currentUrl))) {
|
|
157
|
-
// Forward transformation: URL → tag
|
|
158
|
-
const mediaDetailId = urlToIdMapping[currentUrl];
|
|
159
|
-
if (mediaDetailId) {
|
|
160
|
-
console.log(`Forward: ${currentUrl} → {{media_content(${mediaDetailId})}}`);
|
|
161
|
-
card.media.url = `{{media_content(${mediaDetailId})}}`;
|
|
162
|
-
} else {
|
|
163
|
-
console.warn(`No media detail ID found for URL: ${currentUrl}`);
|
|
164
|
-
}
|
|
165
|
-
} else if (transformDirection === TRANSFORM_DIRECTION.REVERSE ||
|
|
166
|
-
(transformDirection === TRANSFORM_DIRECTION.SMART && extractMediaIdFromTag(currentUrl))) {
|
|
167
|
-
// Reverse transformation: tag → URL
|
|
168
|
-
const mediaId = extractMediaIdFromTag(currentUrl);
|
|
169
|
-
if (mediaId) {
|
|
170
|
-
const actualUrl = idToUrlMapping[mediaId];
|
|
171
|
-
if (actualUrl) {
|
|
172
|
-
console.log(`Reverse: {{media_content(${mediaId})}} → ${actualUrl}`);
|
|
173
|
-
card.media.url = actualUrl;
|
|
174
|
-
} else {
|
|
175
|
-
console.warn(`No URL found for media ID: ${mediaId}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
return newConfig;
|
|
183
|
-
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.error('Failed to fetch media details for transformation:', error);
|
|
186
|
-
|
|
187
|
-
// Fallback: try to extract/convert what we can without API
|
|
188
|
-
console.log('Falling back to URL extraction method');
|
|
189
|
-
newConfig.cards.forEach(card => {
|
|
190
|
-
if (card.media && card.media.url) {
|
|
191
|
-
if (transformDirection === TRANSFORM_DIRECTION.FORWARD) {
|
|
192
|
-
const extractedId = extractMediaIdFromUrl(card.media.url);
|
|
193
|
-
if (extractedId) {
|
|
194
|
-
card.media.url = `{{media_content(${extractedId})}}`;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// For reverse transformation, we can't do much without the API mapping
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// cleanup the newConfig
|
|
202
|
-
delete newConfig.accessToken;
|
|
203
|
-
|
|
204
|
-
return newConfig;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Helper function to extract media ID from media_content tags
|
|
210
|
-
* @param {string} url - The URL containing media_content tag
|
|
211
|
-
* @returns {string|null} - Extracted media ID or null if not found
|
|
212
|
-
*/
|
|
213
|
-
function extractMediaIdFromTag(url) {
|
|
214
|
-
if (!url || typeof url !== 'string') {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Extract media ID from {{media_content(id)}} pattern
|
|
219
|
-
const tagMatch = url.match(/\{\{media_content\(([^)]+)\)\}\}/);
|
|
220
|
-
return tagMatch ? tagMatch[1] : null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Convenience function for reverse transformation: fetches media details and converts tags to URLs in one call.
|
|
225
|
-
* This function forces reverse transformation direction. For automatic detection, use transformTemplateConfigWithMediaDetails.
|
|
226
|
-
* @param {object} templateConfig The template configuration containing media_content tags.
|
|
227
|
-
* @param {string} channel The channel for which to transform the config.
|
|
228
|
-
* @param {string} mediaDetailsId The ID to use when calling getMediaDetails API.
|
|
229
|
-
* @param {object} options Additional options for the transformation.
|
|
230
|
-
* @returns {Promise<object>} Promise that resolves to the template config with actual URLs.
|
|
231
|
-
*/
|
|
232
|
-
export async function convertMediaTagsToUrls(templateConfig, channel, mediaDetailsId, options = {}) {
|
|
233
|
-
return transformTemplateConfigWithMediaDetails(templateConfig, channel, mediaDetailsId, {
|
|
234
|
-
...options,
|
|
235
|
-
forceDirection: TRANSFORM_DIRECTION.REVERSE
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Convenience function for forward transformation: fetches media details and converts URLs to tags in one call.
|
|
241
|
-
* This function forces forward transformation direction. For automatic detection, use transformTemplateConfigWithMediaDetails.
|
|
242
|
-
* @param {object} templateConfig The template configuration containing URLs.
|
|
243
|
-
* @param {string} channel The channel for which to transform the config.
|
|
244
|
-
* @param {string} mediaDetailsId The ID to use when calling getMediaDetails API.
|
|
245
|
-
* @param {object} options Additional options for the transformation.
|
|
246
|
-
* @returns {Promise<object>} Promise that resolves to the template config with media_content tags.
|
|
247
|
-
*/
|
|
248
|
-
export async function convertUrlsToMediaTags(templateConfig, channel, mediaDetailsId, options = {}) {
|
|
249
|
-
return transformTemplateConfigWithMediaDetails(templateConfig, channel, mediaDetailsId, {
|
|
250
|
-
...options,
|
|
251
|
-
forceDirection: TRANSFORM_DIRECTION.FORWARD
|
|
252
|
-
});
|
|
253
|
-
}
|